A focused, fullscreen, side-by-side git review tool for your terminal.
hawkshaw walks you through the unstaged changes in a working tree, one file
at a time, and lets you render a verdict on every hunk before you commit.
When it exits, the index is staged exactly as you decided. You run
git commit yourself.
The scope stays narrow on purpose. There is no branch management, no log
browsing, no sidebar. You read the change, you decide, you move on, with
vim and bat muscle memory.
I review a lot of AI-generated code. You do the same. The terminal tools that already exist pull in two
directions. Full git clients like lazygit and gitui do everything, so they put
a busy general-purpose interface between you and the code. git diff and
git add -p do too little: no real scrolling, no fullscreen reading mode. And their keyboard navigation is not great.
hawkshaw takes one job. It puts a large diff in front of you, hunk by hunk, and keeps the interface out of the way while you work through it.
curl -fsSL https://raw.githubusercontent.com/stojanovic/hawkshaw/main/install.sh | shThe script detects your OS and architecture (Linux and macOS, x86_64 and
arm64), downloads the matching binary from the latest GitHub release, verifies
its SHA-256 against the release checksums before installing anything, and puts
hawkshaw in ~/.local/bin (or /usr/local/bin when run as root).
Running hawkshaw in Docker is the recommended setup. Pin both the script and the binary to the same tag, so nothing moves underneath you:
RUN curl -fsSL https://github.com/stojanovic/hawkshaw/releases/download/v0.1.0/install.sh \
| VERSION=0.1.0 PREFIX=/usr/local shYou need a Rust toolchain. There is no system libgit2 to install first, since
git2 builds a bundled copy.
git clone https://github.com/stojanovic/hawkshaw.git
cd hawkshaw
cargo install --path .That puts the hawkshaw binary in ~/.cargo/bin.
Run it inside a git repository:
hawkshawIt snapshots the working tree at launch, taking modified, untracked, and deleted files. Anything already staged is out of scope. The first file opens fullscreen, and your verdicts hit the index as you make them.
The layout follows the terminal width by default: side-by-side at 120 columns or wider, unified inline below that. Pin it if you would rather decide yourself. See Configuration.
Ignored files disappear the same way git status makes them disappear. That
covers the in-repo .gitignore and .git/info/exclude, and your global
ignore configuration too: core.excludesfile, including a ~-relative path,
plus the conventional ~/.config/git/ignore, ~/.gitignore_global, and
~/.gitignore.
Once every hunk has a verdict, hawkshaw prints a short summary and closes the
case. No need to press q.
hawkshaw --help # keymap and options
hawkshaw --version| Keys | Action |
|---|---|
j / k, ↓ / ↑ |
scroll a line |
F / B |
full page down / up |
Ctrl-d / Ctrl-u, Space / b |
half page down / up |
g / G, Home / End |
top / bottom |
J / K |
next / previous hunk |
h / l, ← / → |
previous / next file |
t |
cycle view: auto, split, inline |
y / n |
stage / skip the current hunk |
Y / N |
stage / skip every remaining hunk in the file |
dd |
discard the current hunk (confirm) |
DD |
discard the whole file back to HEAD (confirm) |
u |
undo the last verdict |
c |
copy the file path and current hunk (as a diff) to the clipboard |
r |
refresh (re-read git status) |
i / I |
add an untracked file, or its directory, to the root .gitignore, then refresh |
? |
help overlay |
q |
quit (warns if hunks are undecided) |
Discards destroy work and always ask first. For an untracked file, a discard deletes the file.
Binary files, images among them, do not split into hunks. hawkshaw shows a
whole-file placeholder that takes the same verdict keys: y/Y stages the
complete file, n/N skips it, dd/DD discards it after confirmation.
c copies the current file path followed by its hunk as a unified diff, ready
to paste into an AI assistant when you want a second opinion on the change. It
writes through the OSC 52 terminal escape, so the system clipboard picks it up
even over SSH, on terminals that support it: iTerm2, kitty, WezTerm, Alacritty,
foot, and tmux with set-clipboard on.
- Two layouts, one keystroke. The side-by-side split puts the old side
on the left, the new side on the right, and aligns changed lines across the
gap. The unified inline view stacks
-over+in one full-width column for narrower terminals, showing each changed block part by part: every removed line, then every added line, the waygit diffreads. A reformatted block stays legible either way. Width picks the layout by default, andtcycles it. - Dark focused diff bands. The focused hunk keeps its syntax highlighting
over dark green and red backgrounds. Everything else recedes to plain
syntax-coloured text with only the gutters tinted, so your eye lands where
the decision is. The
minimaltheme drops the band and leans on the left border alone. - Intra-line highlighting marks the words that actually changed, in a lighter green or red patch that still lets the syntax colours read through.
- Syntax highlighting through
syntect, with dual old and new line-number gutters. - Viewport virtualization. Only the visible window ever renders, so a 10,000-line diff scrolls as fast as a ten-line one.
hawkshaw reads an optional TOML config file, by default from
~/.hawkshaw/config.toml. Point it somewhere else with --config:
hawkshaw --config ./review.toml
hawkshaw --config=./review.tomlThe file takes two optional keys:
| Key | Values | Default | Meaning |
|---|---|---|---|
theme |
default, minimal, noir |
default |
colour scheme |
view |
auto, split, inline |
auto |
diff layout |
theme picks the palette. default is a dark, syntax-friendly scheme with
green and red backgrounds on the focused changed lines. minimal is
high-contrast text with no background fills, marking the focused hunk with
the cursor bar and underlining intra-line changes in bold. noir goes
black-and-white, film-noir greyscale, monochrome down to the syntax, where the
added and removed sides read apart by brightness instead of colour.
high-contrast still works as an alias of default.
view sets the layout. auto, the default, reads the terminal or pane
width and takes the side-by-side split at 120 columns or wider, unified
inline below that. It re-adapts live as you resize, a tmux split included.
split and inline pin one layout. The t key cycles the preference at
runtime, from auto to split to inline.
Both keys are optional. An omitted key falls back to its default, and a missing config file is fine, since the defaults apply throughout. An invalid config, an unknown value or malformed TOML, makes hawkshaw exit with a clear error before it starts rather than quietly ignoring what you wrote.
# ~/.hawkshaw/config.toml
theme = "noir"
view = "inline"Rust, synchronous, no async. ratatui and crossterm drive the TUI, git2
(libgit2) reads status and diffs and applies your verdicts, syntect handles
highlighting, and similar does line pairing and intra-line change detection.
The crate splits into a thin binary and a fully testable library. CI enforces 100% line and function coverage.
I built hawkshaw (no, Claude Code and Codex built it) while working on Competitor Tracker, an app that tracks your competitors and sends you a list of the most important changes every Monday. Competitor Tracker speaks in a film-noir detective voice, so the same noir detective theme carried over to this tool.
Hawkshaw is an old slang word for a detective. It goes back to the detective character Hawkshaw in Tom Taylor's 1863 play The Ticket-of-Leave Man, and it later titled Gus Mager's early twentieth-century newspaper comic strip Hawkshaw the Detective. Of all the noir-detective names on the shortlist, this one sounds good, types easily, sticks in memory, and is unique enough.
Bug reports and pull requests are welcome. Read CONTRIBUTING.md first, particularly the part about the coverage gate, which turns down any code that arrives without a test.
MIT. See LICENSE.