- Shell 100%
| .assets | ||
| .claude | ||
| .gitignore | ||
| CLAUDE.md | ||
| gitprompt.bash | ||
| LICENSE | ||
| README.md | ||
gitprompt.bash
A two-line bash prompt with a compact git status segment.
The image above shows GITPROMPT_MIN_LEVEL=6 (the old GITPROMPT_SHORT preset).
The world does not need another git bash prompt. It already has bash-git-prompt, posh-git-bash, git-radar, gitstatus, starship, oh-my-posh, and a thousand snippets pasted into
~/.bashrcover the last twenty years. So this is just one more — built iteratively with an AI assistant, exactly the way I wanted it. The bar for "yet another prompt" is low when most of the work is conversation.
Layout
<HH:MM> [⌁] <user>@<host>:<cwd> [<env>] ├N <where>[op]<*> <sym> <upstream> <⇣N><⇡N> M<N> ?<N> ⚑<N> ±<N> <duration> (<build>)
$ █
| Segment | Meaning | Color |
|---|---|---|
HH:MM |
current local time (\A); omitted when the cascade reaches level 6 (e.g. via GITPROMPT_MIN_LEVEL=6 or width pressure) |
dim |
⌁ |
shown when $SSH_CONNECTION or $SSH_TTY is set — you're in a remote shell |
dim red |
user@host |
\u@\h; omitted when the cascade reaches level 1 (e.g. via GITPROMPT_MIN_LEVEL=1 or width pressure) |
green |
cwd |
\w, last 3 segments; the cascade progressively shortens it to ~/…/bar style under width pressure |
blue |
env |
active language env: Python (venv) / (conda) and/or Go ⌬<go.work-dir> |
teal |
├N |
linked worktrees; dim when in the main working tree, teal when inside a linked worktree | dim / teal |
where |
branch (or #tag / @sha when detached); appends a teal ◆ when the checked-out branch is exactly tagged |
green / magenta |
[op] |
operation in progress: rebase, merge, cherry-pick, revert, bisect |
red |
+ |
working tree dirty | yellow |
≡ ← → ≠ ? |
sync vs upstream: equal, behind, ahead, diverged, unknown | green / blue / green / red / dim |
upstream |
upstream branch name (OSC 8 hyperlink to the branch page — path picked from git config gitprompt.forge, see Forge detection); collapsed to <remote>/… when the upstream branch matches the local branch |
red |
⇣N ⇡N |
commits behind / ahead | blue / green |
MN |
tracked files with changes | red |
?N |
untracked files | teal |
⚑N |
stash entries | magenta |
±N |
submodules not in clean state | yellow |
<duration> |
runtime of the previous foreground command, if ≥ GITPROMPT_DURATION_THRESHOLD |
dim |
(<build>) |
how long it took to build the git segment itself (ms / s); shown only when ≥ 100 ms | dim |
$ |
shell prompt character (# for root); colored by exit status of the previous command |
green on success / red on failure |
Install
git clone https://example.invalid/gitprompt.git ~/.gitprompt
echo 'source ~/.gitprompt/gitprompt.bash' >> ~/.bashrc
Source it as the last line of ~/.bashrc so nothing else clobbers
PS1 or PROMPT_COMMAND afterwards.
Outside a git working tree, your existing PS1 is restored unchanged —
gitprompt's two-line variant only appears inside a tree.
As a Claude Code statusline
gitprompt.bash auto-detects how it was invoked: sourced from ~/.bashrc
it installs the interactive prompt; executed directly it reads the
statusline JSON from stdin, runs the same renderer, strips the readline
markers, and prints one assembled line.
The executed-mode line is fitted to $COLUMNS (callers like Claude
Code or pi pass their available budget that way; falls back to stty size </dev/tty, then to no limit). When the line doesn't fit, segments
are progressively dropped: self-update hint → build time → command
duration → wall clock → cwd collapses to ~/foo/…/bar → user@host →
language env. The git segment and the SSH marker are never dropped.
Wire it up in ~/.claude/settings.json:
{
"statusLine": {
"type": "command",
"command": "bash ~/.gitprompt/gitprompt.bash"
}
}
Requires jq.
Claude Code only renders the upstream segment's OSC 8 hyperlink as
clickable when it has detected hyperlink support in your terminal.
Windows Terminal (the common WSL host) isn't in that auto-detect list;
the link text appears but doesn't click. Set FORCE_HYPERLINK=1 in your
shell environment before launching Claude Code to override the
detection — export FORCE_HYPERLINK=1 in .bashrc (before the line
that sources gitprompt.bash) is the simplest fix. gitprompt doctor
reports whether the variable is currently set and prints this hint when
it isn't.
Configuration
| Knob | Default | Effect |
|---|---|---|
GITPROMPT_FAST=1 (env) |
unset | Skip git status derived info (dirty marker, MN, ?N, submodules). Use in huge repos. |
git config gitprompt.fast true (per repo) |
unset | Same as above, scoped to one repo. |
GITPROMPT_TIMEOUT (env) |
0.5s |
Time budget for git status. Repos that exceed it auto-degrade to fast mode for the rest of the session. |
GITPROMPT_DURATION_THRESHOLD (env) |
2 |
Show command duration only if it ran for at least this many seconds. |
GITPROMPT_MIN_LEVEL=<n> (env) |
0 |
Force minimum cascade level (0–10). The adaptive cascade pre-applies steps 1..n before the width-driven loop runs from n+1..10. See Adaptive cascade. |
COLUMNS (env) |
(bash-managed) | Read by the adaptive cascade. Callers running gitprompt.bash as a statusline can override COLUMNS to budget the output independently of the terminal width. Falls back to stty size </dev/tty then no limit. |
PROMPT_DIRTRIM (bash) |
3 |
Number of trailing path segments shown in cwd in full mode. gitprompt reads this variable but does not set it; long paths are trimmed with … (U+2026) rather than bash's native .... |
GITPROMPT_SELF_UPDATE=0 (env) |
unset | Disable the self-update nag and the hourly background git fetch. |
GITPROMPT_SELF_UPDATE_INTERVAL (env) |
3600 |
Seconds between background self-update fetches. |
git config gitprompt.selfUpdate false (per repo) |
unset | Per-repo opt-out from the self-update nag. |
git config gitprompt.forge <forge> (per repo) |
unset | Forge backing this repo's origin. Values: github (/tree/), gitlab (/-/tree/), gitea (/src/branch/; forgejo is a synonym). Picks the path layout for the upstream OSC 8 hyperlink. Set automatically by gitprompt autoconf. |
Adaptive cascade
gitprompt fits the prompt to COLUMNS automatically, dropping segments
in a defined order until it fits (or runs out of cuts). Both the
interactive prompt and the executed-mode statusline route through the
same cascade.
A pre-cascade pass first drops diagnostic segments — self-update nag, build time, command duration — because their information value is lowest. Then the formal cascade runs:
| Level | Cut |
|---|---|
| 0 | full |
| 1 | hide user@host |
| 2 | progressive cwd shrink (~/projects/sovereign/leaf → ~/…/sovereign/leaf → ~/…/leaf → leaf) |
| 3 | clock HH:MM → :MM |
| 4 | drop upstream label iff remote is origin AND names match |
| 5 | drop env segment |
| 6 | drop clock entirely |
| 7 | counts → markers (M5 ?2 ⚑1 → M ? ⚑) |
| 8 | drop ⇣N / ⇡N counts, keep direction sym |
| 9 | drop M / ? / ⚑ / ± markers, keep + only |
| 10 | floor: <branch> <sym> |
Level 2 is the only multi-step level: the cascade decrements the "trailing components shown" count one at a time, only moving on to level 3 once the cwd is down to the leaf component (or the line fits, whichever comes first).
Level 4 is conservative on purpose: when you track fork/main or
upstream/master, the remote name is itself information; only origin
collapses cleanly because its label is purely redundant after the
upstream-name-match check.
GITPROMPT_MIN_LEVEL=N forces the cascade to start at level N as a
floor; adaptive shrinking can still go higher if the line still
overflows. gitprompt doctor prints the full cascade preview so you
can see exactly which level your prompt lands at, and what each
level looks like in the current cwd.
Keeping gitprompt up to date
Sourced gitprompt watches its own upstream. Once an hour (configurable
via GITPROMPT_SELF_UPDATE_INTERVAL) it forks a shallow git fetch in
the background; if the local branch is behind, the next prompt appends
a dim hint to the git segment:
… ⚑1 (gitprompt: 1 new — gitprompt update)
The background fetch is silent on every failure (network down, auth
needed, no upstream) — nothing prints, the hint just doesn't appear.
Opt out with GITPROMPT_SELF_UPDATE=0 or
git config gitprompt.selfUpdate false.
The companion gitprompt shell function:
| Subcommand | What it does |
|---|---|
gitprompt update |
Fast-forward (or hard-reset, in shallow clones) the gitprompt repo to @{u}. Refuses on detached HEAD or dirty working tree. |
gitprompt status |
Print resolved repo path, current branch, and behind-count. |
gitprompt doctor |
Print resolved knobs (env + git config) and Claude-Code-relevant environment for diagnosing config. |
gitprompt autoconf |
Detect which forge backs origin and write git config gitprompt.forge. See Forge detection. |
gitprompt man |
Render the embedded man page through man(1) on a tty; print raw nroff source otherwise. |
gitprompt help |
Usage. |
Extra arguments after update replace the default @{u} target, so
gitprompt update origin/main works. The remote is already current from the
background fetch, so git pull is skipped (and wouldn't work in a grafted
shallow clone anyway). The deploy clone is intended to track origin verbatim
— local-only commits there are not preserved when the shallow boundary
forces a hard reset; use a separate full clone for development. After a
successful update the existing mtime self-reload re-sources
gitprompt.bash at the next prompt — no shell restart needed.
Tab completion for gitprompt <subcommand> and the common git pull
flags after gitprompt update is registered automatically when the
script is sourced.
Forge detection
The upstream segment's OSC 8 hyperlink points at the branch page, not the project root, so the path layout has to match the forge software:
| Forge | Path |
|---|---|
| GitHub / GHE | /tree/ |
| GitLab | /-/tree/ |
| Gitea / Forgejo | /src/branch/ |
gitprompt picks the layout in this order:
git config gitprompt.forge(github/gitlab/gitea;forgejois a synonym ofgitea).- Otherwise a hostname heuristic:
github.com⇒ GitHub,gitlab.com⇒ GitLab, anything else ⇒ Gitea/Forgejo.
The hostname heuristic only catches the public instances. Self-hosted
GitHub Enterprise on git.corp.example.com will silently get the wrong
path. Run gitprompt autoconf once per repo to fix it:
$ gitprompt autoconf
gitprompt autoconf: github (probe (https://git.corp.example.com))
With curl available, autoconf probes
https://<host>/api/v1/version (Gitea/Forgejo) and the response
headers of / (GitLab via x-gitlab-meta, GitHub/GHE via
x-github-request-id). Without curl it falls back to the hostname
heuristic — same logic as the prompt path uses, just persisted to
git config so it survives renames and shows up in git config -l.
Flags:
--print— show the detected forge without writing.--unset— cleargitprompt.forge; the prompt falls back to the hostname heuristic on the fly.--no-probe— skipcurleven if installed (offline networks, captive portals).
Performance
Per prompt, in a normal repo, gitprompt issues ~6 git invocations
(branch, op, status, upstream, rev-list, stash, plus optional remote-url
and submodule).
On modern hardware in a small repo this is unnoticeable. In a huge repo
the git status call dominates; gitprompt times it out at
GITPROMPT_TIMEOUT and remembers the repo as "slow" for the rest of the
session, after which only branch / sync / stash are shown. Set
GITPROMPT_FAST=1 or git config gitprompt.fast true to skip the
probe entirely.
If you need a truly fast prompt in massive repos and don't mind a background daemon, use gitstatus instead — it's the right tool for that job.
Files
gitprompt.bash— the prompt itself; sourced as a library, executed as a Claude Code statusline (auto-detected).assets/hero.svg— the picture above
Developing
This repo wires its own Claude Code
status line to gitprompt.bash (in executed mode) via
.claude/settings.json, so opening the project in Claude Code gives
you a live, colored preview of the current rendering at the bottom of
the window — every refresh re-runs the script, so edits show up
immediately without a fresh shell.
gitprompt.bash also self-reloads in interactive shells: every prompt
checks the file's mtime, and if it changed since the last source,
the file is re-sourced before the prompt is built. Edit, save, hit
Enter — no . ~/.bashrc needed.
The self-update nag debounces background fetches via a stamp file at
<git-dir>/gitprompt-self-fetch.stamp inside the gitprompt repo.
gitprompt update removes the stamp after pulling so the next prompt
re-evaluates the behind-count immediately rather than waiting for the
hourly interval.
To opt out of the preview for your local checkout, drop a
.claude/settings.local.json (gitignored) that overrides statusLine,
or remove the project from Claude Code's allowed paths.
License
Apache License, Version 2.0. See LICENSE.