A git plugin that maintains a temporary integration branch named fi. Merge multiple in-progress feature branches together to detect conflicts early and test features in collaboration — before they land on main.
Documentation & install | Specification
This README is for working on git-fi itself. To install and use it, see the documentation site.
Requires Node.js >= 22 and git >= 2.41.0.
The Node floor is the engines field in package.json, and it tracks active LTS: 22 is the oldest line still supported upstream, and CI runs both LTS lines plus Current. Raising it means moving engines and the CI matrix together.
The git floor tracks the newest git feature the code calls rather than a support policy. Today that feature is the %(ahead-behind:<commit-ish>) format atom, which carries the per-branch behind count (READY-01) and which git gained in 2.41.0; next below it are git merge-tree --write-tree (2.38.0), which is how the merge itself is built (MERGE-08), and git branch -r --format= (2.13.0) for the batched branch listing (PERF-01). Dropping the newest of the three would move the floor to 2.38.0, not back to 2.13.0. Reaching for a newer git feature means raising the floor in PRE-02, src/git.ts, and scripts/postinstall.mjs together, and naming the feature that moved it so the next person doesn't have to re-derive the number. The postinstall refuses the install below the floor (PRE-06) and cannot import the number from the build, so a test pins the two against each other.
git clone https://github.com/gettyimages/git-fi.git
cd git-fi
npm install # dev dependencies (tsx, typescript)Day-to-day, git-fi runs straight from source — no global install needed:
npm start -- --help # run src/ directly via tsx
npm run build # compile TypeScript to dist/
npm test # build, then run the integration suiteWith just installed, just lists the same tasks under shorter names — just run --help, just build, just test. The recipes delegate to the npm scripts rather than restating them, so package.json stays the one place a command is defined; CI and prepublishOnly call npm directly either way.
npm test is the primary feedback loop: it drives the compiled binary against throwaway git repositories (a bare origin plus a working clone), exercising real git fetch/merge/push behavior end-to-end. scripts/run-tests.mjs hands node's runner explicit paths rather than a glob, so a pattern that stops matching fails loudly instead of reporting a green suite that ran nothing.
CI runs the suite on Linux and Windows across Node 22, 24, and 26 — both active LTS lines plus Current. Windows is in the matrix because git-fi behaves differently where the platform has no POSIX file modes: it stores the token without setting or checking 0600 (AUTH-04), and --update has to reach npm through the shell there (UPDATE-05).
When something is slow, --debug traces every git command with its elapsed time (OPTION-11) — which is how the fetch's --no-tags came about (PRE-04). How much that flag saves depends on the server, so measure with --debug in the repository in question rather than assuming.
The implementation follows SPEC.md, which defines every requirement with a unique ID and includes mermaid diagrams for the major flows.
To run the checkout as git fi in other repositories for a while:
npm run trial:on # build, npm link, load this copy's completion
npm run trial:off # unlink, restore the version it replacedtrial:on symlinks the checkout onto your PATH, so later npm run builds take effect without reinstalling. It also runs install-completions --write .trial/completions and appends a marked block to ~/.zshrc that puts that directory on your fpath — the same command, writing the same two files, that a user installs with (COMPLETE-06), so a trial exercises the shipped install path rather than a shortcut around it. Only ~/.zshrc is touched; under bash you get the linked binary and wire the completion yourself. trial:off deletes the block and .trial/, then reinstalls the exact version the trial displaced, which trial:on recorded before linking over it (BUILD-03). Where that reference has been copied outside the marked block, trial:off names the lines rather than editing them, since the directory they point at is the one it just removed (BUILD-04). Open a new terminal after either one.
git fi --version is what says which build is in force: a trial answers <version>-dev.g<sha>, an installed copy answers the bare version (BUILD-02).
If git fi runs something other than what you expect, which -a git-fi lists every git-fi on your PATH in resolution order.
The published package — npm install -g @gettyimages/git-fi, documented on the docs site — is the official install for end users. Reach for it here mainly to test the distribution itself; local development runs from source.
SPEC.md Behavioral specification
STATUS.md Requirement coverage
justfile Task runner wrapping the npm scripts
src/ TypeScript implementation
test/ Integration suite (Node test runner)
docs/ Docsify documentation site
scripts/ Build-time generators, the npm postinstall, the trial:on/off helper
man/ Generated man page (git-fi.1)
completions/ Generated bash + zsh completions (_git-fi, _git_fi, git-fi.bash)
The man page and shell completions are generated by scripts/gen-docs.ts, which draws the flag list from src/help.ts — the single source of truth for git-fi's flags. The completion logic itself lives in real shell files under scripts/completion/ (*.tmpl); the generator injects the flag list and stamps a banner. npm run build regenerates the outputs (via npm run gen:docs); those under man/ and completions/ are committed, so edit src/help.ts (flags) or scripts/completion/*.tmpl (completion logic) and rebuild rather than editing the generated files by hand. npm test and CI both run npm run verify:generated, which fails naming any committed artifact that no longer matches what the generator would write, so stale generated files surface before review rather than during it.
Three completion files ship because git fi is dispatched by two different providers: _git-fi drives zsh's built-in _git; git-fi.bash (defining _git_fi, git-completion style) drives git's own completion wrapper in bash; and _git_fi is the same _git_fi body on the zsh fpath, for git's wrapper under zsh. The git-native body (git-fi.bash.tmpl) is shared by the last two, so they can't drift. Each file has an install-completions target that prints it — bash, zsh, zsh-git respectively — and --write <dir> installs the zsh pair onto an fpath directory (COMPLETE-06); adding a file here means adding its target in src/install-completions.ts.
A global npm install copies the zsh pair into <npm prefix>/share/zsh/site-functions from scripts/postinstall.mjs (COMPLETE-07), so completion works with no extra step on the setups that carry that directory on their fpath. It's plain .mjs and lists the two filenames itself because npm runs postinstall before prepare, so dist/ doesn't exist yet on a fresh clone; a test pins its file list to what --write installs. That copy exits 0 on any failure and does nothing for a local install. The git floor check ahead of it (PRE-06) is the one thing in the file that does fail an install, which is the point: a non-zero postinstall stops the install outright, so a git below the floor never gets a git-fi that cannot run on it.
Bug reports and pull requests are welcome on GitHub.
MIT — Copyright (c) 2017-2026 Getty Images.