import { describe, expect, it } from 'vitest' import { decideAlignment } from '../worktree.js' describe('decideAlignment (local develop ↔ origin before a feature PR/merge)', () => { it('no remote ref yet → nothing to do (never block on a missing origin)', () => { expect(decideAlignment({ ahead: 0, behind: 0, remoteExists: false })).toEqual({ action: 'none' }) expect(decideAlignment({ ahead: 3, behind: 5, remoteExists: false })).toEqual({ action: 'none' }) }) it('aligned (0 / 0) → nothing to do', () => { expect(decideAlignment({ ahead: 0, behind: 0, remoteExists: true })).toEqual({ action: 'none' }) }) it('behind only → fast-forward to origin (the stale-local-develop case)', () => { expect(decideAlignment({ ahead: 0, behind: 2, remoteExists: true })).toEqual({ action: 'ff' }) }) it('ahead only → nothing to do (local has unpushed commits, not stale)', () => { expect(decideAlignment({ ahead: 4, behind: 0, remoteExists: true })).toEqual({ action: 'none' }) }) it('diverged (ahead AND behind) → refuse, needs a human', () => { expect(decideAlignment({ ahead: 1, behind: 2, remoteExists: true })).toEqual({ action: 'diverged' }) }) }) // ─── getWorktreePath — parasite-worktree regression (release/5.14.0 incident) ─ // // With worktrees.structure unset, the container dirs derive from the bare repo // root found by walking UP — and that walk must start from the workdir the CLI // acts on (--workdir), not process.cwd(): invoked from the skills install dir, // the old code found no bare root and returned the RELATIVE "5.14.0", which // git resolved UNDER the develop worktree (02-Develop/5.14.0). import { mkdtempSync, mkdirSync, rmSync } from 'fs' import { tmpdir } from 'os' import { join } from 'path' import { getWorktreePath } from '../worktree.js' import type { GitFlowConfig } from '../types.js' function cfg(structure: Partial = {}): GitFlowConfig { return { worktrees: { enabled: true, mode: 'organized', structure: { main: '', develop: '', features: '', releases: '', hotfixes: '', ...structure }, }, } as GitFlowConfig } describe('getWorktreePath (unset structure → derive from the bare root of CWD)', () => { it('resolves an ABSOLUTE container from the workdir passed by the CLI', () => { const root = mkdtempSync(join(tmpdir(), 'gf-root-')) try { mkdirSync(join(root, '.bare')) const workdir = join(root, '02-Develop', 'deep') mkdirSync(workdir, { recursive: true }) expect(getWorktreePath('release/5.14.0', cfg(), workdir)).toBe(join(root, 'releases', '5.14.0')) expect(getWorktreePath('feature/foo', cfg(), workdir)).toBe(join(root, 'features', 'foo')) expect(getWorktreePath('hotfix/bar', cfg(), workdir)).toBe(join(root, 'hotfixes', 'bar')) } finally { rmSync(root, { recursive: true, force: true }) } }) it('returns "" when no bare root is resolvable — NEVER a cwd-relative path', () => { const orphan = mkdtempSync(join(tmpdir(), 'gf-orphan-')) try { const p = getWorktreePath('release/5.14.0', cfg(), orphan) expect(p).toBe('') } finally { rmSync(orphan, { recursive: true, force: true }) } }) it('a configured structure value always wins over derivation', () => { const root = mkdtempSync(join(tmpdir(), 'gf-cfg-')) try { mkdirSync(join(root, '.bare')) const configured = join(root, 'my-releases') const p = getWorktreePath('release/1.2.3', cfg({ releases: configured }), join(root, 'sub')) expect(p).toBe(join(configured, '1.2.3')) } finally { rmSync(root, { recursive: true, force: true }) } }) it('non-flow branches resolve to "" (flat checkout)', () => { expect(getWorktreePath('develop', cfg(), tmpdir())).toBe('') }) })