Open-source project
flamestro/deff avatar
flamestro/deff

deff: a terminal diff viewer built for reviewing a branch before merge

interactive, side-by-side file review for git diffs with per-file navigation, vertical and horizontal scrolling, syntax highlighting, and added/deleted line tinting

587 stars15 forksRustMIT

At a glance

What is it?
deff is a Rust terminal application for reviewing git diffs side by side, with per-file navigation, vim-like motion keys, in-diff search and a reviewed flag that persists under .git so you can work through a branch file by file. It sits in the space between git diff and a pull request review, and its whole design assumes you are reading changes in a terminal rather than in a browser.
Who is it for?
Adopt deff if you review changes in a terminal and want a file-by-file workflow with a persistent reviewed flag, since that combination is what the tool is built around and neither git nor a hosted review tool gives you both. Do not adopt it if your review process lives in a pull request on a hosting platform, since deff has no notion of posting a review, and do not expect it to replace a merge tool, because it only reads and marks.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 111 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

A diff viewer, not a pager and not a merge tool

deff is a Rust TUI, an interactive side-by-side file review for git diffs, with per-file navigation, vertical and horizontal scrolling, syntax highlighting, and added and deleted line tinting. The Cargo manifest describes it as an interactive side-by-side git diff viewer for PR review.

What it is not is as much a part of the description as what it is. It does not merge, it does not stage, it does not commit and it does not talk to a hosting platform. It reads a diff and shows it to you in two panes.

The two panes have independent horizontal scroll offsets, which is the detail that makes the side-by-side layout usable. When a line is long and wrapped, one side can be scrolled while the other stays put, so a hunk in a Rust file and the corresponding hunk in a generated file can be read in step. The README also shows examples of a single-line change view and a multi-line change view, which is the other case the layout has to handle: a change that fits on one line, and a change that does not.

Tinting is at line level, so added lines and deleted lines are coloured differently, and syntax highlighting is language aware. Both are the difference between a wall of coloured text and something you can read, and both come at a cost: the binary carries grammars.

The dependency list in the manifest is short and tells you what the tool is built on. ratatui 0.29 for the terminal rendering, crossterm 0.28 for the terminal handling, clap 4.5 with derive for the argument parsing, syntect 5.3 with default features off and the default-fancy feature enabled for the syntax highlighting, regex for the search, anyhow for error handling and once_cell for lazy statics.

Two strategies, and the uncommitted modes on top

How deff decides what to compare is the first thing to understand, because the default is the one that trips people up.

The upstream-ahead strategy is the default, and it compares your local branch changes against its upstream. The range strategy is the explicit alternative, taking a --base and a --head. On top of those there are two modes that deal with the working tree rather than with commits. An optional --include-uncommitted mode includes working tree and untracked files, and a --only-uncommitted mode compares the working tree and untracked files against HEAD.

The usage block in the README shows the forms side by side:

bash
deff
deff --strategy upstream-ahead
deff --strategy range --base origin/main --head HEAD
deff --strategy range --base origin/main --include-uncommitted
deff --only-uncommitted
deff --theme dark
deff --config ~/.config/deff/config.toml

The --only-uncommitted mode gets its own mention in the quickstart, because it is the one you want before committing: if you have local edits, including untracked files, and want to review them before committing, run deff --only-uncommitted, and it opens the side-by-side review so you can check exactly what changed in your working tree.

The one operational caveat the README calls out is that if your branch has no upstream configured, you use the explicit base and head form instead. That is a reasonable default for a tool that assumes a normal workflow of fetching and rebasing, but it does mean the first run on a fresh branch needs an explicit range.

The uncommitted modes are the part that makes this more than a diff reader. A tool that shows you the difference between two commits is competing with git. A tool that shows you the difference between your working tree and HEAD, before you commit, has a job that git itself does not do well in a terminal.

The reviewed flag is the actual feature

The part of deff that goes beyond showing a diff is the reviewed state, and it is small enough to describe in three lines.

Press r to mark the current file reviewed or unreviewed. The state is persisted locally in .git/deff/reviewed/ and is keyed by comparison scope plus file content hash. Pressing / enters a search query for the current file, which searches both panes, Enter applies the query, and n and N jump between matches.

The keying detail is the interesting part. Keying by comparison scope plus file content hash means that a file you marked reviewed is remembered for that particular review session, and that a file whose contents changed is not treated as already reviewed. The state lives under .git, so it is per repository and does not get committed, and it does not survive a clone.

That design has a consequence worth naming. The reviewed state is local, so a second reviewer on the same repository has their own set of marks, and there is no way to share a review progress with a team. For a solo reviewer working through a large branch, that is exactly right. For a team that wants to see who has signed off on which file, it is the wrong model, and that is a job for a hosting platform's review UI rather than for a terminal tool.

The rest of the interaction model is familiar. Keyboard and mouse navigation including wheel and shift-wheel, vim-like motion navigation with h, j, k and l, g and G, and Ctrl+u and Ctrl+d for paging, plus configurable key bindings.

Syntax highlighting, custom grammars and where the binary weight comes from

The highlighting is built on syntect, and the loading order is documented precisely. deff loads the syntect defaults, the bundled deff grammars, and any extra .sublime-syntax files it finds in assets/syntaxes in the current working directory or in .deff/syntaxes in the current working directory. Any .sublime-syntax file added under the repository's own assets/syntaxes is auto-bundled at build time.

The binary weight follows from that. Grammars are compiled into the binary, and the install script checks for cargo, clones the project into a temporary directory, installs it, and removes the temporary checkout, and the point of the removal being safe is that the bundled grammars are already compiled in, so removing the checkout does not affect highlighting. In other words, you do not need the source tree to run deff, but the first install does need a Rust toolchain, and the resulting binary carries the grammars with it.

The config file is where the key bindings live. The default filesystem locations are ~/.config/deff/config.toml or $XDG_CONFIG_HOME/deff/config.toml, and --config loads a specific file while --no-config ignores config files entirely. The supported actions are listed in full: quit, previous_file, next_file, scroll_up, scroll_down, page_up, page_down, top, bottom, search, next_match, previous_match, next_hunk, previous_hunk and toggle_reviewed. Key names are printable characters such as j, the arrow names, the paging keys and modifiers such as ctrl-u and alt-j. Entries under a keybindings section replace that action's defaults, and actions you leave out keep their default bindings.

The example is short:

toml
[keybindings]
page_down = "J"
page_up = "K"

Note the action names are snake case and quite verbose, which is the price of a scheme that can be extended without guessing what a key means. The theme is a separate axis, with --theme auto, dark or light, a dark syntax theme preferred by default because it reads better on dark terminals, and --theme taking precedence over the DEFF_THEME environment variable.

Installing it, and building from source when you want to change it

There are two install methods and the README presents them as a choice. From crates.io with Cargo:

bash
cargo install deff

Or by running the installer script from the repository:

bash
curl -fsSL https://raw.githubusercontent.com/flamestro/deff/main/install.sh | bash

The second is the one to understand, because it explains what the tool expects of you. The script checks for cargo, clones the project into a temporary directory, installs it, and removes the temporary checkout, and since the grammars are compiled into the binary, removing the checkout does not affect highlighting.

Building locally is the third path and is what a contributor does. The prerequisites are a Rust toolchain with cargo, git, and an interactive terminal. The build and the first check are:

bash
   cargo build --release --locked
   ./target/release/deff --help

The --locked flag matters, since it uses the committed lockfile rather than resolving fresh dependency versions. You can then install it into your local Cargo bin path with cargo install --path ., and run it from inside any git repository you want to review.

The theme is a second configuration layer with a sensible default. By default deff prefers a dark syntax theme, described as better for black and dark terminals, and --theme auto, dark or light controls the rendering for your terminal, with --theme taking precedence over DEFF_THEME set to dark or light.

There is also a GitHub workflow for version bumps, bump-version.yml. It triggers on a push to main, excluding commits authored by the bot, computes the next version from the commit message prefixes with feat mapping to the next minor and fix, chore and docs mapping to the next patch, updates Cargo.toml and Cargo.lock when needed, publishes the new crate version to crates.io, and then creates and pushes the matching tag and GitHub release. That is a simple conventional-commits pipeline, and it means a version in the crate registry always corresponds to a tag.

Where deff is the wrong choice

The first limitation is that a diff is not a review. deff reads a diff and shows it to you, but it has no concept of a comment, a suggestion or an approval. If your process requires that findings reach the author as structured feedback attached to a pull request, deff cannot do that and no amount of configuration will add it. The reviewed flag is a local mark, not a review artefact.

The second limitation is the dependency on a terminal and a git repository. deff needs a TTY, and it needs to be run inside a repository, and the upstream-ahead default needs a branch with an upstream. There is no web interface, no diff over HTTP, and no way to review a patch that is not in a local clone. If your code review happens in a browser tab, this tool is not a substitute for that tab.

The third limitation is that the tool reads from git rather than from anything else. A patch file, a diff between two directories that are not both in git, and a merge conflict view are all outside what it does. The scope is a git diff in a working repository, which is narrow on purpose, and the tool does that one thing well.

The real alternative is a hosted review tool, and the difference in approach is about where the state lives. A hosted platform keeps the diff, the comments, the approvals and the history in a shared server, so a review is a durable artefact that other people can read, and the reviewer's marks are visible to the team. deff keeps everything local, which means no account, no network round trip per diff, no copy of your code on someone else's server, and a review that exists only while you are looking at it. For a solo maintainer or a security reviewer working through a large branch offline, that is the better arrangement. For a team that reviews each other's work asynchronously, the shared state is the feature you cannot do without, and deff is the wrong tool for that job even though it is the better diff reader.

Editorial conclusion

Adopt deff if you review changes in a terminal and want a file-by-file workflow with a persistent reviewed flag, since that combination is what the tool is built around and neither git nor a hosted review tool gives you both. Do not adopt it if your review process lives in a pull request on a hosting platform, since deff has no notion of posting a review, and do not expect it to replace a merge tool, because it only reads and marks. Before you adopt it, check that the branch you care about has an upstream configured, since without one you have to use the explicit range form, and read the config file format for key bindings, because the default action set is deliberately minimal and vim-flavoured.

Frequently asked questions

What is deff?

It is a Rust TUI for interactive, side-by-side file review of git diffs, with per-file navigation, independent horizontal scrolling, syntax highlighting and added or deleted line tinting. The Cargo manifest describes it as an interactive side-by-side git diff viewer for PR review.

How do I install deff?

Either install it from crates.io with cargo install deff, or run the installer script from the repository with curl -fsSL https://raw.githubusercontent.com/flamestro/deff/main/install.sh | bash, which checks for cargo, clones the project into a temporary directory, installs it and removes the checkout, leaving the bundled syntax grammars compiled into the binary.

What is the difference between the deff comparison strategies?

The upstream-ahead strategy is the default and compares your local branch changes against its upstream, while the range strategy takes an explicit --base and --head. On top of those, --include-uncommitted adds working tree and untracked files, and --only-uncommitted compares the working tree and untracked files against HEAD, which is the mode to use before committing.

How does deff track which files I have reviewed?

Press r to mark the current file reviewed or unreviewed. The state is persisted locally in .git/deff/reviewed/ and is keyed by comparison scope plus file content hash, so it is per repository, is not committed, and is not shared with other reviewers.

Can I change deff key bindings and themes?

Key bindings are configured in a TOML file at ~/.config/deff/config.toml or $XDG_CONFIG_HOME/deff/config.toml, with entries under a keybindings section replacing an action's defaults and omitted actions keeping theirs. The theme is set with --theme auto, dark or light, which takes precedence over the DEFF_THEME environment variable, and deff prefers a dark syntax theme by default.

Official sources

  1. flamestro/deff on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/flamestro-deff.svg)](https://hysenlabs.com/projects/flamestro-deff)

Was this useful?