A modern, web-based git visualization tool — a successor to ungit, rebuilt from scratch around a tree-as-hero UI: the commit graph is the whole interface, full-bleed and centered, with no fixed side panels.
- The tree is the interface — branches render as colored "rivers", commits ride their lane, forks and merges curve between them. The checked-out branch is always the leftmost spine; checking out re-lanes the whole graph around the new HEAD.
- Live — chokidar watches the git dir and working tree; every terminal-side change (commit, stage, branch, file edit) appears in ~0.5s over SSE. No refresh button.
- Diff drawer — click a commit dot and the full diff slides in as a dismissible overlay: inline/split modes, syntax highlighting, word-level intra-line highlights, hunk folding, per-file accordions. Esc returns to the tree.
- Working tree — the dashed orange row at the top. Stage/unstage per file or per group, write a message, commit — all without leaving the tree.
- Branch & tag creation — from the commit context menu ("create branch here"), branch pills, or the branches popover. Validated names, optional checkout-after-create.
- Checkout from anywhere — branch pills on the tree, the branches popover (with live filter), remote branches (auto-creates a tracking branch).
- Worktree peek — inspect any linked worktree's uncommitted changes + diffs without switching checkouts (top-right switcher).
- Push / fetch / pull — live top-bar buttons (Pull appears with the ↓behind count; pull is
--ff-onlyuntil merge/rebase land), plus per-branch push and per-remote fetch from the pill menus. When a remote needs credentials, git's askpass prompt becomes a browser modal (masked for passwords/tokens, answered once per operation, never stored) — works for https auth and ssh passphrases viaSSH_ASKPASS. - Focus + collapse — long ref-less runs fold into "⋯ N more commits" breaks; histories load 500 commits at a time with "load more".
- Keyboard —
j/k/arrows step through commits,Escdismisses everything. - Handles detached HEAD, octopus merges, renames, binary files, empty repos, and 30k-commit repos (first paint < 200ms; per-commit stats stream in after).
Git mutations always shell out to your real git (no reimplementation), are serialized against index.lock races, and surface git's own stderr in a toast when something is refused (e.g. dirty-tree checkout).
Install globally and run the progit binary from any repository:
npm i -g @udit_v/progit
progit # in a repo: opens the browser straight on it
progit --port 8000 --no-open
progit --repo /path/to/repo
progit --helpFrom source:
pnpm install
pnpm build
node bin/progit.js # same flags as aboveNavigation is URL-based, like ungit: one server handles any repository on the machine. #/repository?path=/abs/path is the canonical, shareable address of a repo view; the bare URL is a home screen with a path input and your recent repositories. Running the CLI inside a repo just deep-links you there.
The server listens on 8449 by default. If a progit instance already owns the port, a second progit invocation doesn't start another server — it opens a browser tab on the running one, pointed at the repo you invoked it from. If the port is held by some other program, it exits with an error (use --port).
On start, a global install checks npm at most once a day for a newer release and, if one exists, runs npm i -g @udit_v/progit@latest in place (you'll be told to restart progit to pick it up). The check runs in the background and never blocks startup. Source checkouts are skipped. Opt out with --no-update or the PROGIT_NO_UPDATE environment variable.
pnpm dev # tsx server on :3411 + Vite client on :5173 (proxied /api)
pnpm test # vitest: parsers, lane engine, word-diff
pnpm typecheck
scripts/make-fixture.sh [dir] # fabricate a gnarly repo (octopus merge, renames,
# binary, worktree, divergent origin, stash)Browser-driven verification (needs Chrome):
# pass the full repo URL (hash route included)
node scripts/verify-ui.mjs 'http://localhost:3499/#/repository?path=%2Ftmp%2Fprogit-fixture' dirty
node scripts/verify-ui.mjs 'http://localhost:3499/#/repository?path=%2Ftmp%2Fprogit-clean' clean
node scripts/verify-live.mjs 'http://localhost:3499/#/repository?path=%2Ftmp%2Fprogit-fixture' /tmp/progit-fixture
node scripts/verify-remote.mjs 'http://localhost:3499/#/repository?path=%2Ftmp%2Fprogit-fixture' /tmp/progit-fixture # push/fetch/pull + credential modalvscode-extension/ is a companion extension that runs progit inside an editor
tab. The progit: Open Repository View command (Command Palette, or the
branch icon in the Source Control title bar) reuses or spawns a progit server
for the workspace folder and renders the UI in a Webview deep-linked to that
repo. It expects progit on PATH (falls back to npx -y @udit_v/progit); see
vscode-extension/README.md for settings and packaging (npm run package).
The package publishes to npm. Both paths run prepublishOnly
(typecheck → test → build):
pnpm release patch # bump, tag, push → CI publishes on the tag
pnpm release minor --local # ...and publish from this machine instead.github/workflows/publish.yml publishes with provenance on any v* tag push
(needs an NPM_TOKEN secret) and skips versions already on the registry.
Single package, two halves sharing src/shared/types.ts:
- Server (
src/server/) — Hono on Node, repo-agnostic: every API call carries?path=, and a registry (src/server/repos.ts) lazily resolves each path to its repo root, caching a git runner + filesystem watcher per repo. Every endpoint shells out togitviaexecFile(arg arrays only, validated ref names, paths after--). Parsers forlog(%x1f/%x1eformat strings),for-each-ref,status --porcelain=v2 -z,worktree list --porcelain, and unified diff output live insrc/server/parse/. A chokidar watcher per repo debounces git-dir + working-tree events into one SSE stream (/api/events?path=). - Client (
src/client/) — React 19 + Vite + TanStack Query. SSE events invalidate queries by scope. The lane engine (src/client/graph/lanes.ts) re-lanes the graph client-side on every checkout: the current branch's first-parent chain is column 0, other branches fan out ordered by fork depth; branch colors are a stable name hash so they survive re-lanes and restarts. Per-commit+/-stats load progressively (/api/log/stats) because--numstatcosts 10–20× the log itself on large repos.
The UI is a faithful port of the Ungit Redesign v3 prototype in design-ref/ (see its README); the design CSS is used nearly verbatim (src/client/styles/).
Merge and rebase (with conflict continue/abort), cherry-pick, revert, reset, branch/tag delete, discard/patch-level staging, stash management, remotes/submodules, and drag-a-ref-onto-a-node interactions. The corresponding menu items are visible but disabled with an "M2" hint.
