diffo is a terminal Git diff review tool for local review before commit or push. It opens the current working tree diff by default, including untracked files, supports git diff-style targets, tracks file-level reviewed state, stores inline comments, and exposes review data through script-friendly CLI commands.
The project is implemented in Zig and targets Zig 0.16.0.
Use diffo when you want a fast local pass over changes before a commit, push, or pull request. It keeps the review loop in the terminal, remembers which file patches you have already checked, and stores comments outside the repository so review notes do not dirty the working tree.
- Review unstaged, staged, and untracked working tree changes by default.
- Review explicit Git targets such as
HEAD~1..HEAD, branches, ranges,--cached, and pathspecs. - Browse diffs in a terminal UI with a right-side file list.
- Toggle inline and split diff views.
- Mark files as reviewed or unreviewed.
- Automatically invalidate reviewed state when a file patch changes.
- Add single-line or multi-line comments from the TUI or CLI.
- List comments and review state as text or JSON for scripts and agents.
- Store state outside the repository using XDG state paths.
- Use Catppuccin Mocha as the built-in default theme.
- Validate Base16/Base24 theme files.
- Apply syntax highlighting when terminal color support is available. Bundled Tree-sitter grammars are used for Zig, TypeScript/TSX, JavaScript, Rust, C, C++, and Python files.
Requirements:
- Zig
0.16.0 - Git
- A POSIX-like terminal for the interactive TUI
Install Zig with Homebrew:
brew install zigIf Zig is already installed through Homebrew:
brew upgrade zig
zig versionBuild from source:
zig buildThe executable is written to:
zig-out/bin/diffoInstall to ~/.local/bin with just:
just installOr install manually after building:
mkdir -p ~/.local/bin
cp zig-out/bin/diffo ~/.local/bin/diffo
chmod +x ~/.local/bin/diffoMake sure ~/.local/bin is on your PATH before running diffo from any repository.
Run tests:
zig build testFrom inside a Git repository:
diffoOr, from this project checkout:
./zig-out/bin/diffoReview an explicit target:
diffo HEAD~1..HEAD
diffo main...feature
diffo --cached
diffo -- src/When stdout is not a TTY, diffo renders a static diff screen instead of entering the interactive alternate screen.
| Key | Action |
|---|---|
j / k |
Move down / up within the current file diff |
↑ / ↓ |
Move down / up within the current file diff |
G / gg |
跳到当前文件 diff 的底部 / 顶部 |
PageUp / PageDown |
Scroll by a larger step |
J / K |
Move to next / previous file |
n / N |
Jump to next / previous change |
C |
Toggle unfold / fold mode |
z |
Toggle the fold at the cursor in fold mode |
Z |
Toggle all folds in the current file in fold mode |
v |
Toggle stacked / split diff view |
r |
Toggle current file reviewed state |
c |
Add a comment at the current diff line |
V |
Start a multi-line selection |
y |
Copy the selected diff rows, or the current row, to the clipboard |
Esc |
Clear the current selection |
u |
Jump to the first unreviewed file |
? |
Toggle help |
q |
Quit |
Mouse support is enabled in the TUI:
- Scroll wheel over the diff scrolls the current file.
- Scroll wheel over the file tree moves between files.
- Clicking a diff row moves the cursor.
- Dragging over diff rows extends the selection for copying or commenting.
- Clicking a file-tree row opens that file.
When running inside tmux, mouse events must be enabled in tmux as well:
set -g mouse onShow help:
diffo --helpList comments:
diffo comments list
diffo comments list --file src/main.zig
diffo comments list --jsonGet one comment:
diffo comments get cmt_0123456789abcdef
diffo comments get cmt_0123456789abcdef --json清理当前 review target 中已经过期的注释(锚点状态为 stale 或 missing),或用 --all 删除仓库中保存的所有注释:
diffo comments clean
diffo comments clean --all
diffo comments clean --dry-run
diffo comments clean --file src/main.zig
diffo comments clean --jsonAdd a comment without opening the TUI:
diffo comments add --file src/main.zig --line 42 --body "Check this branch."
diffo comments add --file src/main.zig --line 42 --end 45 --body "Consider extracting this block."Show review status:
diffo review status
diffo review status --file src/main.zig
diffo review status --jsonMark a file:
diffo review mark --file src/main.zig --reviewed
diffo review mark --file src/main.zig --unreviewedTheme commands:
diffo themes list
diffo themes validate path/to/theme.yamlDebug Git command failures:
diffo --debug-git
diffo review status --debug-gitReview status output includes stable top-level fields:
{
"schema_version": 1,
"repository_id": "repo_...",
"review_target_id": "target_...",
"files": [
{
"file_path": "src/main.zig",
"status": "unreviewed",
"patch_fingerprint": "sha256:...",
"comment_count": 0
}
]
}Comment output includes file path, line range, side, body, author, match status, and anchor data:
{
"comment_id": "cmt_...",
"file_path": "src/main.zig",
"start_line": 42,
"end_line": 45,
"side": "new",
"body": "Consider extracting this block.",
"author": "like",
"match_status": "exact",
"anchor": {
"hunk_header": "@@ -40,3 +42,6 @@",
"patch_fingerprint": "sha256:...",
"stable_line_ids": ["line_..."]
},
"review_target_id": "target_..."
}match_status can be:
exact: the stored patch fingerprint still matches.stale: the file still exists in the current diff, but the patch changed.missing: the file no longer exists in the current diff.relocated: reserved for future relocation support.
diffo does not write review data into the Git working tree. State is stored under:
${XDG_STATE_HOME:-$HOME/.local/state}/diffo/repos/<repo_id>/
Current files:
comments.json
review-states.json
Writes use a temporary file followed by rename, so interrupted writes should not corrupt repository contents.
- Tree-sitter highlighting is currently bundled for Zig, TypeScript/TSX, JavaScript, Rust, C, C++, and Python files; unsupported languages, explicit targets, large files, and unavailable source sides are shown without syntax highlighting.
- Comment relocation is minimal: comments become
stalewhen the patch fingerprint changes. - Runtime theme switching and full config loading are not implemented yet.
- Mouse support covers click selection and scroll wheel navigation; richer filtering/search can be added later.
Format sources:
zig fmt build.zig src/*.zigRun the normal validation loop:
zig build test
zig build