Build GitKraken
A local Git client whose hard problems are parsing real repository state without lying about filenames or signatures, drawing a commit graph from the parent list rather than from dates, and making every destructive action recoverable by something better than hope.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Everything this client does is git doing it, and porcelain v2 plus the signature placeholders need a version that has them.
Get it Your package manager, or git-scm.com. open ↗
Verify
git --version prints the version, and git status --porcelain=v2 -z runs without error - installfree
Why Runs the local process serving the interface.
Get it nodejs.org or your version manager. open ↗
Verify
node --version prints v22 or higher - have readyfree
Why Lane assignment, rename detection and unmerged states are the three places this build breaks, and none of them appear in a fresh repository with four commits.
Get it Clone something real, or generate one with a script in tests/fixtures.
Verify
It contains at least one merge with two parents and one rename git detects - have readyfree
Why A filename containing a newline is the single fastest way to find out whether your status parser is correct, and it costs one shell script.
Get it Create files with spaces, quotes, non-ASCII characters and a newline in the name.
Verify
git status --porcelain=v2 -z lists them and your parser agrees on the count - decidefree
Why Mapping the eight signature values is easy to get wrong and easy to test with a stub, but a real expired or revoked key is the only way to see the real output.
Get it Decide between fixture commits with recorded output or generating a key you later revoke.
Verify
The test suite covers all eight values whichever route you take
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- repos(id, path, name, added_at, last_opened_at) · the list of folders you opened, and nothing about what is in them - settings(id, graph_window, fetch_interval_seconds, confirm_destructive, always_show_command) - recovery(id, repo_id, at, action, ref_name, head_before, argv_json, undone_at) · one row per destructive action and the ref that reverses it - command_log(id, repo_id, at, argv_json, exit_code, duration_ms, stderr_tail) · every invocation, so a surprise is explainable rather than arguable - Nothing about commits, branches, diffs or file contents is stored here. git is the store, and a cache of git is a second source of truth that will eventually disagree with the first. - The repository list holds paths. A client that copies your code into its own database has quietly become a new place your code can leak from.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
GIT_BIN | required | /usr/bin/git | The binary every command goes through. Pinned so you know what you tested. |
BIND_ADDRESS | required | 127.0.0.1 | Loopback. Changing this exposes filesystem and git access to your network. |
DATABASE_PATH | required | ./data/client.db | Repository paths, settings, recovery rows and the command log. No code. |
RECOVERY_REF_PREFIX | required | refs/vibe-recovery | Where undo refs are written. Listed in the README so you can clean them up. |
GRAPH_WINDOW | required | 500 | Commits read per page. The reason a large repository opens quickly. |
GIT_TERMINAL_PROMPT | required | 0 | Set for every child process, so git never blocks waiting for a password. |
COMMAND_TIMEOUT_MS | required | 30000 | After this a command is killed and its partial output kept. |
The build, in order
Repository state, read honestly
The working tree view matches what git actually reports, including for files nobody expects.
Split on NUL and read the record type from the first field of each entry.
Files
src/git/status.tstests/status-weird-names.test.tsterminalgit status --porcelain=v2 -z --branch --untracked-files=all
A test that only uses readme.txt proves nothing about this code.
Files
tests/fixtures/make-repo.shA git status every second on a large repository is a fan you can hear.
Files
src/git/watch.ts
done when · tick each as it passeswatch out- Parsing git status --porcelain by splitting the output on newlines breaks on a filename containing a newline, which is legal on every filesystem you will meet, and without -z git quotes unusual pathnames the way core.quotePath describes, so you also get backslash escapes you then render as literal text; the docs say plainly that there is an alternate -z format recommended for machine parsing, so use --porcelain=v2 -z and split on NUL.
A graph drawn from parents, not from dates
The commit graph is correct on a repository with merges and is bounded on one with a hundred thousand commits.
One process, one parse, a known field order.
Files
src/git/log.tsterminalgit log --max-count=500 -z --pretty=format:%H%x00%P%x00%D%x00%an%x00%cI%x00%s
Open a lane on a first parent, close it when a commit claims it.
Files
src/graph/lanes.tstests/lanes.test.tsOctopus merges and criss-cross history are where lane code falls over.
Files
tests/fixtures/merge-history.sh
done when · tick each as it passeswatch out- Sorting the commit list by date to draw it produces a graph where a commit appears above its own parent, because author dates are whatever the committing machine said they were and a rebase or a wrong clock makes a child older than its parent; take the order git returns, which is already topological enough to draw, and treat dates as a label rather than as an ordering.
Diffs and staging, including the files that break diff views
Staging a hunk changes the index and nothing else, and a binary file never reaches the renderer.
The index is the only thing a staging action is allowed to change.
Files
src/git/stage.tstests/stage-hunk.test.tsterminalgit apply --cached --unidiff-zero -
Decoding a forty megabyte binary as UTF-8 freezes the tab.
Files
src/diff/guard.tsA moved file shown as a delete and an add loses its history in the eye.
Files
src/git/diff.ts
done when · tick each as it passeswatch out- Applying a partially staged hunk by writing a modified copy of the file back to the working tree destroys whatever else the user had unsaved in that file and quietly changes line endings on a repository with autocrlf set; build a patch containing only the selected hunk and apply it to the index with git apply --cached, so the working tree is never touched by a staging operation.
Destructive actions with a written way back
Nothing that can lose work runs without a recovery ref and a visible command.
If the ref write fails the action must not happen at all.
Files
src/safety/recovery.tstests/recovery.test.tsterminalgit update-ref refs/vibe-recovery/$(date +%s) HEAD
It is the only case git itself cannot recover.
Files
src/safety/stash.tsterminalgit stash push --include-untracked --message
The first question after any surprise is what was actually run.
Files
src/git/run.tssrc/ui/CommandPreview.tsx
done when · tick each as it passeswatch out- Treating the reflog as the undo feature means the day someone discards uncommitted changes there is nothing to recover, because those changes were never an object git knew about, and the day someone comes back a month later the reflog entry has expired under the repository's own gc settings; write your own ref for committed state, stash for uncommitted state, and never offer a destructive action that has neither.
Remotes, signatures and the parts that hangproduct builder
Fetching never blocks the interface, credentials stay with the credential helper, and a signature tick means what it says.
B, U, X, Y, R and E are not the same thing as G.
Files
src/git/signatures.tstests/signatures.test.tsterminalgit log --max-count=50 --pretty=format:%H%x00%G?%x00%GS%x00%GK
A client that collects passwords is a password collector.
Files
src/git/run.tsdocs/credentials.mdA fetch against an unreachable remote must not freeze the window.
Files
src/git/run.ts
done when · tick each as it passeswatch out- Showing a green tick whenever the signature status is anything other than N is wrong in five distinct ways, because git reports G for a good signature but also B for a bad one, U for good with unknown validity, X for good but expired, Y for good made by an expired key, R for good made by a revoked key and E for one it could not check at all; map each of the eight values to its own state, and give a tick only to G.
That is the whole plan for GitKraken. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- A Git reimplementation against a library binding.
- Hosted issue and pull request integrations.
- Shared cloud workspaces.
- A built-in editor.
- frontier coding model quality
- large-context infrastructure
- IDE-wide polish and latency
- enterprise policy, telemetry, and support
- Worktree support, which is genuinely useful and mostly a matter of teaching the repository list that one folder can have several checkouts
- A commit-graph write on open for large repositories, measured before and after rather than assumed
- Interactive rebase as a list you reorder, built only after the recovery refs have survived a few months of ordinary use
Need the files? The project pack on the verdict page hands your agent the whole brief · more dev tools.