# How Bat's Diff Highlighting Works: A Technical Deep Dive into the Source Code

> Discover how bat's diff highlighting works! Explore the source code to understand libgit2, HashMap, and LineChangesDecoration for advanced Git diff output.

- Repository: [David Peter/bat](https://github.com/sharkdp/bat)
- Tags: deep-dive
- Published: 2026-03-06

---

**Bat's diff highlighting works by using libgit2 to compute changes between the working directory and Git index, storing results in a `HashMap<u32, LineChange>`, and decorating output lines with colored symbols via the `LineChangesDecoration` struct.**

When you pass the `--diff` flag to **bat**, the syntax-highlighting cat clone transforms into a powerful diff viewer. This feature, implemented in the `sharkdp/bat` repository, leverages Rust's `git2` crate to analyze modifications and render per-line change indicators directly in your terminal.

## The Architecture of Bat's Diff Highlighting

The diff highlighting pipeline spans six distinct modules, moving from CLI argument parsing through Git integration to terminal output decoration. At each stage, specific structs and functions transform raw Git data into visual indicators.

The core flow follows this path: CLI flags in [`clap_app.rs`](https://github.com/sharkdp/bat/blob/main/clap_app.rs) set `diff_mode` → [`controller.rs`](https://github.com/sharkdp/bat/blob/main/controller.rs) triggers `get_git_diff` → [`src/diff.rs`](https://github.com/sharkdp/bat/blob/main/src/diff.rs) builds a line-change map → [`printer.rs`](https://github.com/sharkdp/bat/blob/main/printer.rs) passes the map to `InteractivePrinter` → [`decorations.rs`](https://github.com/sharkdp/bat/blob/main/decorations.rs) renders symbols.

## Step 1: CLI Flag Parsing and Configuration

### Defining the --diff Option in clap_app.rs

Bat defines the diff functionality through Clap arguments in [`src/bin/bat/clap_app.rs`](https://github.com/sharkdp/bat/blob/main/src/bin/bat/clap_app.rs). Lines 155-171 declare both the `--diff` (`-d`) flag and the `--diff-context` option for controlling surrounding line display.

When users invoke `bat --diff`, the argument parser sets internal configuration values that propagate through the application. The `Config::visible_lines.diff_mode()` method returns `true` when this flag is present, signaling downstream components to activate diff processing.

### Configuring Diff Mode in config.rs

The `Config` struct in [`src/config.rs`](https://github.com/sharkdp/bat/blob/main/src/config.rs) maintains the `diff_mode` state throughout the application lifecycle. This boolean flag determines whether the controller should attempt Git diff computation or skip directly to standard file printing.

## Step 2: Triggering the Diff in the Controller

The `Controller` struct in [`src/controller.rs`](https://github.com/sharkdp/bat/blob/main/src/controller.rs) orchestrates the diff detection logic. Between lines 163-180, the code checks `diff_mode()` and conditionally calls `get_git_diff(path)` to retrieve change information for the target file.

If the file resides in a Git repository with uncommitted changes, `get_git_diff` returns `Some(HashMap<u32, LineChange>)` mapping line numbers to change types. The controller passes this map into the printer initialization, ensuring the decoration layer receives the diff data.

The optional `diff_context` value controls how many unchanged lines appear around modifications, though the core highlighting logic functions independently of context length.

## Step 3: Computing the Diff with libgit2

### The diff.rs Module and HashMap Storage

The [`src/diff.rs`](https://github.com/sharkdp/bat/blob/main/src/diff.rs) module contains the core Git integration logic, utilizing the `git2` crate (Rust bindings for libgit2) to compute differences without shelling out to the `git` binary. Lines 19-84 handle the repository discovery and diff generation.

The module computes a diff between the Git index and the working tree (or HEAD for staged changes). It iterates through diff hunks, recording specific line numbers for additions, deletions, and modifications. These results populate a `HashMap<u32, LineChange>` where keys are 1-based line numbers and values indicate the change type.

### LineChange Enum and Symbol Mapping

The `LineChange` enum defines four distinct states that map to specific visual symbols:

- **Added lines**: Displayed with a green `+` symbol
- **Removed lines above**: Displayed with a red `‾` (overline) symbol  
- **Removed lines below**: Displayed with a red `_` (underscore) symbol
- **Modified lines**: Displayed with a yellow `~` symbol

This mapping occurs in [`src/decorations.rs`](https://github.com/sharkdp/bat/blob/main/src/decorations.rs), where the `LineChangesDecoration` struct translates the raw `LineChange` values into terminal-ready strings with appropriate ANSI color codes.

## Step 4: Decorating Output with LineChangesDecoration

### InteractivePrinter Setup in printer.rs

The `InteractivePrinter` struct in [`src/printer.rs`](https://github.com/sharkdp/bat/blob/main/src/printer.rs) receives the diff data through its constructor (lines 95-100). It accepts an `Option<LineChanges>` reference—the `HashMap` produced by the diff module—and stores it for use during the printing lifecycle.

The printer also defines the color scheme for diff symbols in the `Colors::colored` method (lines 332-342), assigning green to additions, red to removals, and yellow to modifications. Users can override these colors via custom themes or the `BAT_THEME` environment variable.

### Rendering Symbols in decorations.rs

The actual symbol generation happens in [`src/decorations.rs`](https://github.com/sharkdp/bat/blob/main/src/decorations.rs) through the `LineChangesDecoration` struct. Lines 72-99 handle the decoration creation, which only occurs when both `config.style_components.changes()` is enabled and the diff map contains data.

During line printing, `InteractivePrinter::print_line` invokes `LineChangesDecoration::generate` (lines 101-124). This method looks up the current line number in the diff map:

- If the line is **added**, it returns a green `+`
- If the line is **removed above**, it returns a red `‾`  
- If the line is **removed below**, it returns a red `_`
- If the line is **modified**, it returns a yellow `~`
- If unchanged, it returns a blank space

The symbol is printed immediately before the line number and content, creating the side-by-side diff visualization that bat is known for.

## Usage Examples and Implementation Details

You can trigger bat's diff highlighting from the command line using the `--diff` or `-d` flag. This mode works particularly well when reviewing changes before committing or when browsing modified files in a Git repository.

```bash

# Basic usage – show a git diff with bat’s pretty colours

bat --diff README.md

# Show 3 lines of context around each change (default is 0)

bat --diff --diff-context=3 src/main.rs

# Disable the diff symbols while keeping the rest of the styling

BAT_THEME="OneHalfDark" bat --diff --style=plain README.md

```

Under the hood, bat avoids shelling out to the `git` binary by using the `git2` crate, which provides direct bindings to libgit2. This approach ensures cross-platform consistency and eliminates the performance overhead of spawning external processes.

The following Rust snippet demonstrates the core logic that bat uses in [`src/diff.rs`](https://github.com/sharkdp/bat/blob/main/src/diff.rs) to build the line-change map:

```rust
// Minimal snippet that mimics bat’s diff handling
use git2::{Repository, DiffOptions};
use std::collections::HashMap;

#[derive(Copy, Clone)]
enum LineChange { Added, RemovedAbove, RemovedBelow, Modified }

fn get_git_diff(path: &std::path::Path) -> Option<HashMap<u32, LineChange>> {
    let repo = Repository::discover(path).ok()?;
    let workdir = repo.workdir()?;
    let diff_opts = {
        let mut o = DiffOptions::new();
        o.pathspec(path.strip_prefix(workdir).ok()?.into_c_string().ok()?);
        o.context_lines(0);
        o
    };
    let diff = repo.diff_index_to_workdir(None, Some(&mut diff_opts)).ok()?;
    let mut map = HashMap::new();

    diff.foreach(
        &mut |_, _| true,
        None,
        Some(&mut |_, hunk| {
            // Simplified: mark every line in the hunk as Modified
            let start = hunk.new_start();
            let end   = start + hunk.new_lines() - 1;
            for l in start..=end { map.insert(l, LineChange::Modified); }
            true
        }),
        None,
    ).ok()?;
    Some(map)
}

```

## Summary

Bat's diff highlighting combines Git integration with terminal decoration to provide immediate visual feedback on file modifications. The implementation relies on several key architectural decisions:

- **CLI Integration**: The `--diff` flag in [`src/bin/bat/clap_app.rs`](https://github.com/sharkdp/bat/blob/main/src/bin/bat/clap_app.rs) activates `diff_mode` in the configuration, triggering the entire pipeline.
- **Git Analysis**: The [`src/diff.rs`](https://github.com/sharkdp/bat/blob/main/src/diff.rs) module uses libgit2 via the `git2` crate to compute differences without external process calls, storing results in a `HashMap<u32, LineChange>`.
- **Symbol Decoration**: The `LineChangesDecoration` struct in [`src/decorations.rs`](https://github.com/sharkdp/bat/blob/main/src/decorations.rs) translates change types into colored symbols (`+`, `‾`, `_`, `~`) that appear beside line numbers.
- **Color Management**: The `InteractivePrinter` in [`src/printer.rs`](https://github.com/sharkdp/bat/blob/main/src/printer.rs) applies green for additions, red for removals, and yellow for modifications, respecting user themes via `BAT_THEME`.

## Frequently Asked Questions

### How do I enable diff highlighting in bat?

Pass the `--diff` or `-d` flag when invoking bat. For example, `bat --diff src/main.rs` displays the file with colored symbols indicating which lines differ from the Git index. You can combine this with `--diff-context=N` to show N lines of surrounding context.

### What do the symbols in bat's diff output mean?

Bat uses four distinct symbols to represent change states: a green **+** indicates added lines, a red **‾** (overline) marks removed lines appearing above the current hunk, a red **_** (underscore) marks removed lines below, and a yellow **~** denotes modified lines. Unchanged lines display a blank space in the symbol column.

### How does bat compute diffs without calling the git binary?

Instead of spawning a `git` subprocess, bat uses the `git2` crate, which provides Rust bindings to libgit2. In [`src/diff.rs`](https://github.com/sharkdp/bat/blob/main/src/diff.rs), the code calls `Repository::discover()` to find the Git root, then `repo.diff_index_to_workdir()` to generate the diff programmatically. This approach ensures cross-platform consistency and better performance by avoiding process overhead.

### Can I customize the colors used for diff highlighting?

Yes, bat respects the `BAT_THEME` environment variable and custom theme files defined in your configuration directory. The default colors are defined in [`src/printer.rs`](https://github.com/sharkdp/bat/blob/main/src/printer.rs) within the `Colors::colored` method: green for additions, red for removals, and yellow for modifications. You can override these by creating a custom theme or selecting a built-in theme that provides different color mappings for diff components.