rtk-ai/rtk · error

--numstat -p must parse

Error message

--numstat -p must parse

What it means

`.expect("--numstat -p must parse")` panic: `condense_unified_diff_strict()` returned None on a `git log --numstat -p` fixture. The challenge is a mixed stream: a `--numstat` block (including `-<TAB>-<TAB>b.bin`, the binary marker) precedes the patches, and the condenser must consume numstat facts and the patches together. None indicates the numstat prelude derails header detection.

Solutions

  1. Teach the strict condenser to skip/collect `--numstat` lines (tab-separated, with `-` placeholder for binary) until the first `commit`/`diff --git` boundary.
  2. Treat `-<TAB>-<TAB>path` as a binary fact, not a malformed header.
  3. Verify sub-commit boundaries in `git log -p` streams reset state between patches.
  4. Add a standalone test for the numstat prelude scanner.

Example fix

// before
let lines = diff.split('\n').collect(); // first line '3\t1\tpath' => no header => None
// after
let lines = skip_numstat_prelude(diff).split('\n').collect();
Defensive patterns

Strategy: validation

Validate before calling

assert!(condense_unified_diff_strict(&numstat_p_fixture)
    .map_or(false, |o| o.contains("[file] b.bin (binary)") && o.contains("[file] d/e/f (+1 -0)")));

Type guard

fn can_condense(diff: &str) -> bool { crate::cmds::git::diff_cmd::condense_unified_diff_strict(diff).is_some() }

Try / catch

let out = match condense_unified_diff_strict(input) {
    Some(o) if o.len() < input.len() => o,
    _ => input.to_string(),
};

Prevention

When it happens

Trigger: Calling `condense_unified_diff_strict()` on tests/fixtures/diff/git_log_numstat_p_raw.txt returns None because the leading numstat lines (`added<TAB>deleted<TAB>path`, with `-` for binary) are not recognized, so the parser never enters patch mode and yields None. The expected output mixes `[file] b.bin (binary)` and patch-derived entries like `[file] d/e/f (+1 -0)`.

Common situations: Running `rtk git log --numstat -p`; combining stat-style producers with `-p` patches; changes to numstat/binary-marker handling in the diff filter.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of rtk-ai/rtk@0924356b4c (2026-09-19). Data as JSON: /api/errors/94c592317eed63e2. Report an issue: GitHub.

Appendix: source

Thrown at src/cmds/git/diff_cmd.rs:6611

        // `git diff --cached -M --no-prefix`: `diff --git src.txt dst.txt`
        // shares no tail; `rename to` names it and the pair may not.
        let np = include_str!("../../../tests/fixtures/diff/git_diff_no_prefix_rename_raw.txt");
        let out = condense_unified_diff_strict(np).expect("must parse");
        assert!(
            out.contains("[file] dst.txt (renamed from src.txt) (+1 -0)"),
            "got:\n{out}"
        );
        // `--submodule=diff`: nested column-0 sections under the range.
        let sd = include_str!("../../../tests/fixtures/diff/git_diff_submodule_diff_raw.txt");
        assert_eq!(
            condense_unified_diff_strict(sd).as_deref(),
            Some(
                "[file] o.txt (+1 -1)\n-k\n+k2\n[file] sub (submodule, 0641fef..19e2e29)\n[file] sub/s.txt (+1 -1)\n-s\n+s2\n[file] sub/sb.bin (binary)"
            )
        );
        // `git log --numstat -p`: `-<TAB>-<TAB>b.bin` before the diff.
        let ns = include_str!("../../../tests/fixtures/diff/git_log_numstat_p_raw.txt");
        let out = condense_unified_diff_strict(ns).expect("--numstat -p must parse");
        assert!(out.contains("[file] b.bin (binary)"), "got:\n{out}");
        assert!(out.contains("[file] d/e/f (+1 -0)"), "got:\n{out}");
        // `hg log -p --template '{node|short} {desc}\n'`: a prose prologue
        // with no `changeset:` line, then hg's echo — prose is not a fact.
        let tmpl = include_str!("../../../tests/fixtures/diff/hg_log_p_template_raw.txt");
        let out = condense_unified_diff_strict(tmpl).expect("templated hg log must parse");
        assert!(out.contains("[file] bin.dat (binary)"), "got:\n{out}");
        assert!(out.contains("[file] f.txt (+1 -1)"), "got:\n{out}");
        // `LC_ALL=fr_FR.UTF-8 diff -u`: the marker is localized, position
        // decides — kept after `-x` and after `+y`.
        let fr = include_str!("../../../tests/fixtures/diff/diff_u_fr_no_newline_raw.txt");
        assert_eq!(
            condense_unified_diff_strict(fr).as_deref(),
            Some(
                "[file] n2 (+1 -1)\n-x\n\\ Pas de fin de ligne à la fin du fichier\n+y\n\\ Pas de fin de ligne à la fin du fichier"
            )
        );
        // A git binary section followed by another producer's header pair

View on GitHub (pinned to 0924356b4c)