Skip to content

git-scenarios

Real git repos. Any state. One line of code. Composable atoms for merge conflicts, submodules, remotes, in-progress operations — for tests, demos, and tool development.

Install in seconds

Terminal window
npm install --save-dev @gfargo/git-scenarios simple-git

The problem

Every git tool — TUIs, IDEs, CLIs, linters — behaves differently against different repo states. Testing those behaviors means:

Hand-rolled setup

80+ lines of git initwriteFilecommit per test. Drift between tests is inevitable.

Checked-in fixtures

Real .git directories in your repo. Bloats the tree, breaks on updates, no programmatic access.

Mocking git

Tests verify “my code called the right command” — not “my code does the right thing in this state.”

Docker containers

Pre-baked images with git state. Heavy, slow, CI-only. Not interactive-test-friendly.

The solution

@gfargo/git-scenarios gives you real git repos in any state with one line of code:

import { spinUpScenario } from '@gfargo/git-scenarios'
// One line — real repo, real refs, real worktree
const repo = await spinUpScenario('mid-merge-conflict')
// repo is mid-merge with src/widget.ts conflicted, MERGE_HEAD set

Or compose your own state from atoms:

import { chain, addCommit, switchToBranch, startMerge, createTempGitRepo } from '@gfargo/git-scenarios'
const repo = await createTempGitRepo()
await chain(
addCommit({ message: 'base', files: { 'x.ts': 'base\n' } }),
switchToBranch('feat/theirs'),
addCommit({ message: 'theirs', files: { 'x.ts': 'theirs\n' } }),
switchToBranch('main'),
addCommit({ message: 'ours', files: { 'x.ts': 'ours\n' } }),
startMerge('feat/theirs'),
)(repo)

What you get

27 curated scenarios

Branch states, merge/rebase/cherry-pick/revert conflicts, bisect, stash, worktrees, submodules, shallow clones, large histories. All deterministic. Browse all →

50+ composable atoms

Build any git state from small, typed building blocks. chain() them together. Write your own — they’re just functions. Atom reference →

Jest adapter

describeWithScenario('name', (getRepo) => { ... }) — zero-boilerplate test setup with automatic cleanup. Setup guide →

Tool-agnostic CLI

npx git-scenarios create mid-merge-conflict --run "lazygit" — materialize any scenario and launch your tool against it. CLI docs →

Custom registration

registerScenario(myScenario) — your custom scenarios work with spinUpScenario, fromScenario, and the CLI. Learn more →

Dual CJS/ESM

Ships both formats. Use import or require — both work. TypeScript-first with full type declarations. Install →

Quick CLI demo

Terminal window
# Spin up a merge conflict, launch lazygit against it
npx git-scenarios create mid-merge-conflict --run "lazygit"
# List all 27 scenarios
npx git-scenarios list
# Describe a specific scenario's contracts
npx git-scenarios describe feature-pr-ready

Built for tool authors

Originally extracted from coco, an AI-powered git CLI/TUI with 16 specialized views. Every view needed deterministic git states to test against. After writing the same git init + writeFile + commit setup 47 times, the scenario library was born.

Now it’s a standalone package for anyone building git tools — TUIs, IDE extensions, CLI utilities, linters, formatters, or anything that reads repo state.