Skip to content

difftastic

difftastic (command: difft) is a structural diff tool that compares files based on their syntax tree rather than line-by-line text. Written in Rust, it understands the grammar of over 50 programming languages to show meaningful changes like moved code, renamed variables, and reformatted blocks.

Installation

# Using cargo (Rust)
cargo install difftastic

# macOS
brew install difftastic

# Ubuntu/Debian
sudo apt-get install difftastic
# Or download from releases
curl -sSfL https://github.com/Wilfred/difftastic/releases/latest/download/difft-x86_64-unknown-linux-gnu.tar.gz | \
    tar xz -C /usr/local/bin/

# Arch Linux
sudo pacman -S difftastic

# Nix
nix-env -i difftastic

# Verify installation
difft --version

Basic Usage

# Compare two files
difft old_file.py new_file.py

# Compare two directories
difft old_dir/ new_dir/

# Read from stdin (use - for stdin)
echo "fn main() {}" | difft - new_file.rs

Git Integration

Configure as Git Difftool

# Set difftastic as the default git difftool
git config --global diff.tool difftastic
git config --global difftool.difftastic.cmd 'difft "$LOCAL" "$REMOTE"'
git config --global difftool.prompt false

# Use with git difftool
git difftool          # Compare working tree vs staged
git difftool HEAD     # Compare working tree vs HEAD
git difftool HEAD~1   # Compare HEAD vs HEAD~1
git difftool main..feature  # Compare branches

Configure as Default Git Diff

# Set difftastic as the default diff driver via GIT_EXTERNAL_DIFF
# Add to your shell profile (~/.bashrc, ~/.zshrc):
export GIT_EXTERNAL_DIFF=difft

# Now all git diff commands use difftastic
git diff
git diff HEAD~1
git diff main..feature
git log -p  # Show patches with difftastic
git show    # Show commit with difftastic

.gitconfig Setup

# Add to ~/.gitconfig

[diff]
    tool = difftastic

[difftool]
    prompt = false

[difftool "difftastic"]
    cmd = difft "$LOCAL" "$REMOTE"

# Optional: create alias for convenience
[alias]
    dft = difftool
    dlog = "!f() { GIT_EXTERNAL_DIFF=difft git log -p --ext-diff $@; }; f"
    dshow = "!f() { GIT_EXTERNAL_DIFF=difft git show --ext-diff $@; }; f"
# Use the aliases
git dft                  # Structural diff
git dlog                 # Log with structural diffs
git dlog -5              # Last 5 commits with structural diffs
git dshow HEAD           # Show latest commit with structural diff
git dshow abc123         # Show specific commit

Supported Languages

difftastic supports 50+ languages including:

Bash          C             C++           C#
Clojure       CMake         CSS           Dart
Elixir        Elm           Erlang        Go
Haskell       HCL           HTML          Java
JavaScript    JSON          Julia         Kotlin
LaTeX         Lua           Make          Nix
OCaml         Perl          PHP           Python
R             Ruby          Rust          Scala
SQL           Swift         TOML          TypeScript
YAML          Zig           and more...

# Check if a language is supported
difft --list-languages

# difftastic auto-detects language from file extension
# Falls back to text-based diff for unsupported languages

Display Modes

Side-by-Side (Default)

# Side-by-side display (default)
difft --display side-by-side old.py new.py

# Or equivalently
difft old.py new.py

Inline

# Inline display (shows changes in sequence, like unified diff)
difft --display inline old.py new.py

# Useful for narrow terminals or piping
difft --display inline old.py new.py | less

Side-by-Side Only

# Side-by-side with no syntax highlighting for unchanged parts
difft --display side-by-side-show-both old.py new.py

Color Output

# Color output (enabled by default for terminals)
difft old.py new.py

# Force color output (for piping)
difft --color always old.py new.py

# Disable color
difft --color never old.py new.py

# Color when piped to less
difft --color always old.py new.py | less -R

Width Control

# Set display width (default: terminal width)
difft --width 120 old.py new.py

# Narrow display for small terminals
difft --width 80 old.py new.py

# Wide display for large monitors
difft --width 200 old.py new.py

# Tab width (default: 4)
difft --tab-width 2 old.py new.py

Context Lines

# Set number of context lines around changes (default: 3)
difft --context 5 old.py new.py

# Minimal context
difft --context 0 old.py new.py

# More context
difft --context 10 old.py new.py

Language Override

# Force a specific language parser
difft --language python old.txt new.txt

# Useful when file extension doesn't match content
difft --language javascript config.txt config_new.txt

# Use text-based diff (disable structural parsing)
difft --language text old.py new.py

Handling Large Diffs

# Set byte limit for files (skip very large files)
difft --byte-limit 1000000 old_file new_file

# Set graph limit (controls structural diff accuracy vs speed)
difft --graph-limit 500000 old_file new_file

# For very large files, fall back to text diff
difft --language text large_old.json large_new.json

Practical Examples

# Compare before/after refactoring
difft src/old_module.py src/new_module.py

# Review a specific commit structurally
GIT_EXTERNAL_DIFF=difft git show abc123

# Compare branches
GIT_EXTERNAL_DIFF=difft git diff main..feature -- "*.py"

# Compare config file changes
difft /etc/nginx/nginx.conf /etc/nginx/nginx.conf.new

# Check formatting-only changes (structural diff ignores whitespace)
# Reformat a file and diff - only real changes shown
black old.py  # Format with black
difft old.py.bak old.py  # Shows no diff if only formatting changed

# Compare JSON structures
difft config_v1.json config_v2.json

Why Structural Diff

Benefits over traditional line-based diff:

1. Ignores formatting changes
   - Reformatted code shows no diff if logic unchanged
   - Whitespace-only changes are hidden

2. Understands code structure
   - Moved functions are shown as moves, not delete+add
   - Renamed variables shown inline

3. Better noise reduction
   - Comment changes don't obscure code changes
   - Import reordering shown cleanly

4. Language-aware matching
   - Matches corresponding brackets, blocks, functions
   - Understands string literals vs code

Limitations:
- Slower than line-based diff for very large files
- Some languages may not be fully supported
- Graph limit may cause fallback to text diff

Environment Variables

# Set default display mode
export DFT_DISPLAY=inline

# Set default width
export DFT_WIDTH=120

# Set default color mode
export DFT_COLOR=always

# Set default context lines
export DFT_CONTEXT=5

# Set default tab width
export DFT_TAB_WIDTH=4

# Set byte limit
export DFT_BYTE_LIMIT=1000000

# Set graph limit
export DFT_GRAPH_LIMIT=500000