# How bat Retrieves and Displays Git Diff Information

> Learn how bat retrieves and displays Git diff information by discovering repositories, generating diffs with git2, and color-coding highlights with InteractivePrinter.

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

---

**bat discovers the closest Git repository using `Repository::discover`, generates a diff between the index and working directory via `git2`, and maps changed line numbers to color-coded highlights through the `InteractivePrinter`.**

The `bat` command-line tool enhances standard file viewing with syntax highlighting and integrated Git diff visualization. When you run `bat` on a file within a Git repository, it can highlight added, removed, and modified lines directly in the terminal output. According to the sharkdp/bat source code, this functionality is only compiled when the **git** feature is enabled (`#[cfg(feature = "git")]`) and operates through a three-stage pipeline involving repository discovery, line change mapping, and conditional rendering.

## Step 1: Repository Discovery and Diff Generation in src/diff.rs

The process begins in [`src/diff.rs`](https://github.com/sharkdp/bat/blob/main/src/diff.rs) with the `diff::get_git_diff(path)` function. This function first locates the containing repository using `Repository::discover`, which walks up the directory tree until it finds a `.git` folder.

Once discovered, `bat` converts the absolute file path into a repository-relative `pathspec` required by the Git2 library. It then configures a `git2::DiffOptions` object with `context_lines(0)` to ensure only the exact changed lines are reported initially. Finally, it calls `repo.diff_index_to_workdir` to produce a `git2::Diff` representing changes between the Git index and the working tree.

## Step 2: Mapping Line Changes to a HashMap

After generating the diff, `bat` walks through the changes using `diff.foreach`. For each hunk, the code compares `old_lines` versus `new_lines` to classify changes into a `LineChanges` type, which is aliased as `HashMap<u32, LineChange>`.

The mapping logic categorizes each line into one of four states:

- **Added** – Lines that exist only in the working tree (new additions).
- **RemovedAbove** – Indicates the entire file was deleted (no new lines, line 1 is marked).
- **RemovedBelow** – Lines removed in-place where the new start equals 0.
- **Modified** – Any other mix of old and new lines representing content changes.

This `LineChanges` map is returned as `Option<LineChanges>` and provides the canonical record of which line numbers require highlighting during output.

## Step 3: Integrating Diff Data in src/controller.rs

The `print_input` method in [`src/controller.rs`](https://github.com/sharkdp/bat/blob/main/src/controller.rs) determines when to fetch and apply Git diff information. The system checks two configuration flags before calling `get_git_diff`:

1. `config.visible_lines.diff_mode()` – True when running with `--diff` (which sets `VisibleLines::DiffContext`).
2. `config.style_components.changes()` – True when using `--style=changes`.

When either condition is met and the input is an ordinary file (not stdin), the `LineChanges` are converted into `LineRange` objects that include configurable context lines around each change (controlled by `VisibleLines::DiffContext(usize)`). These ranges are passed to the `InteractivePrinter`, which renders **added** lines in green, **removed** lines in red, and **modified** lines in yellow.

## Command-Line Usage Examples

You can trigger Git diff visualization using several flags:

```bash

# Show only lines around Git changes (default 3 lines of context)

bat --diff path/to/file.rs

# Show the full file with changed lines highlighted

bat --style=changes path/to/file.rs

# Customize the number of context lines around changes

bat --diff-context=5 path/to/file.rs

```

The underlying Rust logic that gates this behavior demonstrates the conditional check:

```rust
#[cfg(feature = "git")]
let line_changes = if config.visible_lines.diff_mode()
    || config.style_components.changes() {
    match opened_input.kind {
        crate::input::OpenedInputKind::OrdinaryFile(ref path) => get_git_diff(path),
        _ => None,
    }
} else {
    None
};

```

## Summary

- **bat** uses conditional compilation (`#[cfg(feature = "git")]`) to include Git diff functionality only when explicitly enabled during the build process.
- The `get_git_diff` function in [`src/diff.rs`](https://github.com/sharkdp/bat/blob/main/src/diff.rs) handles repository discovery via `Repository::discover` and generates diffs using `git2::DiffOptions` with zero context lines initially.
- Line changes are categorized into **Added**, **RemovedAbove**, **RemovedBelow**, or **Modified** states and stored in a `HashMap<u32, LineChange>`.
- In [`src/controller.rs`](https://github.com/sharkdp/bat/blob/main/src/controller.rs), the `print_input` method conditionally retrieves diffs when `--diff` or `--style=changes` flags are active.
- The `InteractivePrinter` consumes these line changes to color-code output (green for additions, red for deletions, yellow for modifications).

## Frequently Asked Questions

### How does bat detect if a file is inside a Git repository?

`bat` calls `Repository::discover` from the `git2` crate, which traverses up the directory tree from the target file until it locates a `.git` directory. This establishes the repository context required to generate relative paths and compute accurate diffs.

### What Git library does bat use for diff generation?

The implementation relies on the **git2** crate, which provides Rust bindings for libgit2. Specifically, `bat` uses `git2::DiffOptions` to configure the diff parameters and `repo.diff_index_to_workdir` to compare the staged index against the working directory.

### Why does bat set `context_lines` to 0 when generating the diff?

Setting `context_lines(0)` on the `git2::DiffOptions` object ensures that only the exact changed lines are reported from the Git library. `bat` handles context line display separately through its own `VisibleLines::DiffContext` configuration, allowing users to customize context size via flags like `--diff-context` without re-querying the repository.

### How can I view only the changed lines without the full file content?

Use the `--diff` flag (or `--diff-context=N` for custom context size). This activates `VisibleLines::DiffContext` mode, which filters the output to show only line ranges surrounding Git modifications rather than rendering the entire file.