DSA DSA In Depth
0%
case git-diffmyerspatience-diff

Production Case Study: Inside git diff (Myers vs Patience vs Histogram Diff)

7 min read Module 4.2: Suffix Structures, Sequence Alignment & Data Compression Updated Oct 23, 2026
📄 Cheat Sheet
How Computers Compress Text: Huffman Coding and Huffman Trees
Tom Scott How Computers Compress Text: Huffman Coding and Huffman Trees Watch on YouTube ↗

Compares git diff --myers, --patience, and --histogram on real C/Rust refactoring commits to show how low-frequency anchor lines prevent misaligned closing braces.

0:00 / --:--

1. The Boilerplate Misalignment Problem in Pure Myers Diff

Suppose File $A$ and File $B$ differ because a developer rewrote the body of func_one() while adding func_two(). Inside those functions sit dozens of identical boilerplate lines (}, return err;, blank lines). Because pure Myers diff treats every line as an unweighted token and greedily slides along the first available diagonal snake, it readily matches the closing } of func_one() in File $A$ with the closing } of func_two() in File $B$, producing a tangled, unreadable diff hunk that splits across function boundaries.

2. Git's Three Production Diff Engines

  1. git diff --myers (Default / xdl_do_diff): Hashes each line into an integer token ID, strips common prefix/suffix lines, and runs Bidirectional Myers $O(ND)$ with Hirschberg's linear-space midpoint split (find_snake). Fast when $D$ is small, but includes heuristic cutoffs (max_chain_length) when $D$ is huge so it doesn't stall in $\Theta(N^2)$ time on completely rewritten files.
  2. git diff --patience (Bram Cohen):
    • Identify all lines that occur exactly once in File $A$ AND exactly once in File $B$.
    • Find the Longest Increasing Subsequence (LIS) of their line positions in $O(U \log U)$ time via Patience Sorting (hence the name!).
    • Use those matched unique lines as immovable structural anchors, and recursively diff the sub-slices between consecutive anchors (falling back to Myers when no unique common lines remain in a slice).
  3. git diff --histogram (JGit / Git 1.7.7+): What if a slice has no lines with count $== 1$ (e.g., a file with two similar classes where every header appears twice)? Patience Diff immediately falls back to Myers. Histogram Diff generalizes Patience Diff: it builds a frequency histogram of all lines in $A$, scans for the common line with the lowest occurrence count (up to a threshold of 64 occurrences) that forms the longest contiguous matching block, splits both files around that anchor block, and recurses on the prefix and suffix!
Git Algorithm Flag Anchor Selection Strategy Worst-Case / Typical Speed Human Readability on Code
--myers (default) None (all lines weighted equally) $O((N + M)D)$ (needs cutoffs if $D \approx N$) Can misalign braces {, }, blank lines
--patience LIS of lines with $\text{freq}_A = \text{freq}_B = 1$ $O(N \log N)$ on unique anchors + Myers Excellent on unique function/struct headers
--histogram Longest block anchored on lowest-frequency line Often faster than Myers on large diffs! Best overall; handles repeated blocks cleanly
Patience Anchor Order-Preservation Invariant

Let $(a_1, b_1), (a_2, b_2), \dots, (a_k, b_k)$ be the pairs of line indices for lines occurring uniquely in both $A$ and $B$, sorted by $a_1 < a_2 < \dots < a_k$. A subset of these unique matches can appear together in a valid non-crossing diff alignment if and only if their $B$-indices form a strictly increasing subsequence $b_{i_1} < b_{i_2} < \dots < b_{i_m}$. Finding the Longest Increasing Subsequence (LIS) in $O(k \log k)$ time maximizes the number of preserved structural anchors.

Line Hashing & Post-Diff Slider Heuristics in libxdiff

Before running any diff algorithm, Git's libxdiff hashes every line (using a fast multiplicative hash) into a 32-bit bucket ID so the inner diff loop compares uint32_t integers in registers rather than calling memcmp on line strings. After computing the edit script, Git runs a slider post-processing pass (xdl_change_compact) that shifts ambiguous deletion/insertion hunks up or down when surrounded by identical lines (such as }) so the hunk aligns cleanly with blank lines and indentation boundaries.

3. Visual Trace: Myers Brace Misalignment vs. Patience/Histogram Anchor

git diff --myers (Matches wrong '}' brace) fn first() { + return 1; +} +fn second() { return 2; } <-- Myers matched old '}' to second()! git diff --patience / --histogram fn first() { [Unique Anchor #1] - return 2; + return 1; } +fn second() { + return 2; +}
flowchart TD A["Input Slice A[a0..a1], B[b0..b1]"] --> B["Strip matching prefix and suffix lines"] B --> C["Build frequency histogram of remaining lines in A"] C --> D{"Found common line with count <= 64?"} D -- "No (all lines absent or ultra-repetitive)" --> E["Fallback to Myers O(ND) on slice"] D -- "Yes" --> F["Pick longest contiguous match block anchored on lowest-frequency line"] F --> G["Recurse on Prefix Slice (before block) and Suffix Slice (after block)"]
Myers O(ND) Edit Graph (k-Diagonals) & Huffman Tree / LZ77 Sliding-Window Deflate
O((N+M)D) Myers Diff · Huffman + LZ77

Explore Eugene Myers’ k = x − y diagonal edit graph with zero-cost diagonal snakes (used by git diff), bottom-up min-heap Huffman optimal prefix code trees, and LZ77 sliding-window (distance, length) back-references.

Step 1 / 9
Custom Input:
Timeline

Invariant & State

Myers Edit Graph (A="ABCABBA", B="CBABAC"): diagonal k = x − y. At d=0 edits, start at (0,0); A[0]='A' != B[0]='C' so snake length = 0.
Myers Edit Graph (k = x − y Diagonals) — Edit Distance Budget d = 0 Path: (0,0) → (0,0) Diagonal Snake: while A[x]==B[y] → (x+1, y+1) at Cost 0!
[0]k=-1: —
[1]k=0: V[0]=0
[2]k=+1: —
Edit Budget d
d = 0
Active Diagonal k
k = x − y = 0
Furthest Reach
(0,0) → (0,0)
Complexity
O((N + M) · D)
[Step 1] d = 0: start at (0,0) on diagonal k = 0. No immediate diagonal match.

4. Reference Implementation: Patience Diff Unique-Line LIS Anchor Finder

#include <algorithm>
#include <string_view>
#include <unordered_map>
#include <utility>
#include <vector>

struct AnchorMatch {
    int line_a;
    int line_b;
};

// Finds Patience Diff structural anchors: Longest Increasing Subsequence of unique common lines
std::vector<AnchorMatch> find_patience_anchors(const std::vector<std::string_view>& a,
                                               const std::vector<std::string_view>& b) {
    std::unordered_map<std::string_view, int> count_a, count_b, pos_b;
    for (int i = 0; i < static_cast<int>(a.size()); ++i) count_a[a[i]]++;
    for (int j = 0; j < static_cast<int>(b.size()); ++j) {
        count_b[b[j]]++;
        pos_b[b[j]] = j;
    }

    // Collect pairs (i, j) for lines occurring exactly once in A and once in B
    std::vector<AnchorMatch> uniques;
    for (int i = 0; i < static_cast<int>(a.size()); ++i) {
        if (count_a[a[i]] == 1 && count_b[a[i]] == 1) {
            uniques.push_back({i, pos_b[a[i]]});
        }
    }
    if (uniques.empty()) return {};

    // Patience Sorting O(U log U) Longest Increasing Subsequence on line_b
    const int u = static_cast<int>(uniques.size());
    std::vector<int> tails, tail_idx, parent(u, -1);
    for (int k = 0; k < u; ++k) {
        int y = uniques[k].line_b;
        auto it = std::lower_bound(tails.begin(), tails.end(), y);
        int idx = static_cast<int>(it - tails.begin());
        if (it == tails.end()) {
            tails.push_back(y);
            tail_idx.push_back(k);
        } else {
            *it = y;
            tail_idx[idx] = k;
        }
        if (idx > 0) parent[k] = tail_idx[idx - 1];
    }

    std::vector<AnchorMatch> anchors;
    for (int cur = tail_idx.back(); cur != -1; cur = parent[cur]) {
        anchors.push_back(uniques[cur]);
    }
    std::reverse(anchors.begin(), anchors.end());
    return anchors;
}
Patience and Histogram Diff Do Not Guarantee Minimum Edit Distance

By forcing the alignment path to pass through low-frequency structural anchors (like function declarations), Patience Diff and Histogram Diff deliberately sacrifice mathematical shortest-edit-script optimality when matching a dozen } braces across functions would save 2 lines in the diff! In version control and code review, semantic alignment of unique lines is far more valuable than saving 2 lines by scrambling function boundaries.

Check yourself Question 1 of 4
Concept Check Why Pure Myers Diff Misaligns Closing Braces `{` / `}` recall

When comparing two versions of a source file where a function was rewritten and another function was added below it, `git diff --myers` frequently pairs the old function's closing `}` with the new function's closing `}`.

⚡ Interactive Challenge · Staff Level

Algorithmic Design & Complexity Check

Analyze the invariant, asymptotic bound, and hardware behavior before selecting.

What is the asymptotic cost of exact Myers diff when $D = \Theta(N + M)$, and how does Git's `libxdiff` prevent `git diff` from hanging?

Key Takeaways

Pure Myers diff weights every line equally, so high-frequency boilerplate lines ('}', blank lines) can hijack diagonal snakes and misalign function boundaries.
Patience Diff finds the Longest Increasing Subsequence (LIS) of lines occurring exactly once in both files in O(U log U) time and recursively diffs the intervals between those anchors.
Histogram Diff generalizes Patience Diff by anchoring on the longest contiguous match containing the lowest-frequency line (up to 64 occurrences), often running faster than Myers on large diffs.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue