Skip to content

Scenarios Overview

The library ships 46 curated scenarios across 6 kinds. Each produces a deterministic git repo state — same setup, same result, every time.

By kind

Branch (14 scenarios)

NameWhat you get
empty-repoFreshly-initialized repo: no commits, no files, no remotes. HEAD on main but unborn.
feature-pr-readyfeat/widget-v2 4 commits ahead of main, clean worktree.
feature-branch-one-commitmain + feat/x (1 commit ahead).
multi-commit-branchfeat/dashboard with 8 varied commits.
two-commit-featureBaseline + a feat commit on main, clean worktree.
orphan-branchmain + gh-pages orphan branch with no shared history.
branch-tracking-upstreammain tracks origin/main, both at same commit.
branch-ahead-of-upstreammain is 3 commits ahead of origin/main.
branch-behind-upstreammain is 3 commits behind origin/main.
branch-divergedmain is 2 ahead AND 2 behind origin/main.
multi-remote-with-trackingFork-workflow: origin + upstream remotes with tracking.
branch-sync-showcaseFive branches in five different sync states.
detached-headHEAD detached at main~2.
signed-commits-requiredcommit.gpgsign=true + user.signingkey set.

Operation (6 scenarios)

NameWhat you get
mid-bisect20 commits + active git bisect, HEAD at midpoint.
mid-merge-conflictIn-progress merge with 1 conflict on src/widget.ts.
mid-rebase-conflictIn-progress rebase with 1 conflict on src/config.ts.
mid-cherry-pick-conflictIn-progress cherry-pick with 1 conflict on src/utils.ts.
mid-revert-conflictIn-progress revert with 1 conflict on src/service.ts.
interactive-rebase-mid-editInteractive rebase paused at an edit action; 2 picks remain.

History (7 scenarios)

NameWhat you get
merge-no-conflictCompleted --no-ff merge of feat/x into main (2-parent commit at HEAD).
rich-history-graph20+ commits, 2 --no-ff merges, 1 unmerged branch.
chip-rendering-showcase6 commits with different ref-chip kinds (HEAD, local, remote, tag).
shallow-clone10 commits but only 4 reachable (.git/shallow set).
large-repo115 commits across 3 branches with 3 tags.
commits-with-notes2 commits with git notes; first commit also has a note in refs/notes/ci.
mixed-tagsLightweight (v0.1.0), annotated (v1.0.0), and tree-pointing (tree-snapshot) tags.

Worktree (9 scenarios)

NameWhat you get
single-staged-fileBaseline + 1 staged README.
partial-stage2 staged + 2 unstaged + 1 untracked — the “mixed worktree” shape.
monorepo-multi-packageWorkspaces monorepo: app clean, lib staged, cli unstaged.
dirty-many-files12 staged + 6 unstaged + 3 untracked files.
multiple-worktreesPrimary worktree + 3 linked worktrees.
locked-worktreePrimary worktree + 1 linked worktree locked with a reason.
git-lfs-pointerLFS pointer file committed for a binary asset — no git-lfs binary required.
crlf-normalization.gitattributes with text=auto eol=lf normalising all text files.
case-collisiongit history holds src/File.ts and src/file.ts — a case-only collision visible on case-insensitive FS.
installed-hookspre-commit and commit-msg hooks installed and executable — for testing hook detection and --no-verify flows.

Stash (2 scenarios)

NameWhat you get
stashed-changesClean main + 3 stashes (LIFO ordered).
stash-with-untrackedOne stash containing both modified tracked + untracked new files.

Submodule (2 scenarios)

NameWhat you get
submodule-with-historyParent + vendor/lib submodule (4 commits, branch = main).
out-of-date-submoduleParent pinned to an older submodule SHA; git submodule status shows +.

Using scenarios

// One-shot
const repo = await spinUpScenario('mid-merge-conflict')
// With options
const repo = await spinUpScenario('feature-pr-ready', {
remote: 'git@github.com:org/repo.git',
autoCleanup: true,
})
// With extra steps
const repo = await fromScenario('feature-pr-ready',
addCommit({ message: 'extra', files: { 'x.ts': 'x\n' } }),
)
// Via any framework adapter (Jest, Vitest, node:test, Mocha)
describeWithScenario('mid-merge-conflict', (getRepo) => {
it('has conflicts', async () => {
const repo = getRepo()
const status = await repo.git.status()
expect(status.conflicted.length).toBeGreaterThan(0)
})
})

Filtering by tag

Every scenario carries one or more tags for finer-grained filtering:

import { findScenariosByTag, findRegisteredByTag } from '@gfargo/git-scenarios'
// All conflict scenarios (built-ins only)
const conflicts = findScenariosByTag(['conflict'])
// → mid-merge-conflict, mid-rebase-conflict, mid-cherry-pick-conflict, mid-revert-conflict
// Same, but searches custom-registered scenarios too
const allConflicts = findRegisteredByTag(['conflict'])
// Match ALL tags
const dirtyMonorepos = findScenariosByTag(['monorepo', 'dirty'], 'all')

Common tags:

TagScenarios
conflictAll four mid-*-conflict scenarios
dirty / staged / unstaged / untrackedWorktree scenarios
upstream / tracking / behind / ahead / divergedBranch tracking scenarios
merge / rebase / cherry-pick / revertOperation-specific
history / large / performanceHistory-shape scenarios
cleanScenarios with a clean worktree
monorepo / workspacesMonorepo-shaped

The CLI exposes the same filtering:

Terminal window
npx git-scenarios list --tag conflict
npx git-scenarios list --kind worktree --tag dirty