rtk-ai/rtk · error

templated hg log must parse

Error message

templated hg log must parse

What it means

`.expect("templated hg log must parse")` panic: `condense_unified_diff_strict()` returned None on an `hg log -p --template '{node|short} {desc}\n'` fixture. The stream has a prose prologue with no `changeset:` line, followed by hg's echo of the template, then patches; the condenser must treat that prose as noise (not facts) and still condense the patches. None means the prose prologue forced the whole-stream raw fallback.

Solutions

  1. Open a message/prose region on recognized producer headers even without a `changeset:` line (as done for mbox `From`), so templated prologues are skipped rather than fatal.
  2. Only force raw fallback when unread column-0 noise appears after a diff context starts, not before the first header.
  3. Regenerate the fixture from the exact hg version/template if the recorded echo format drifted.
  4. Add a fixture-less unit test: prose + patches must condense.

Example fix

// before
if !saw_changeset_header { dropped_prologue = true; } // later => raw fallback
// after
if !saw_changeset_header { open_message_region(); } // prose skipped, patches still condensed
Defensive patterns

Strategy: validation

Validate before calling

assert!(condense_unified_diff_strict(&hg_template_fixture)
    .map_or(false, |o| o.contains("[file] f.txt (+1 -1)")), "templated prologue must not force raw");

Type guard

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

Try / catch

let out = condense_unified_diff_strict(hg_output).unwrap_or_else(|| hg_output.to_string());
// production callers never panic: condense_stdin already does this (diff_cmd.rs:498)

Prevention

When it happens

Trigger: Calling `condense_unified_diff_strict()` on tests/fixtures/diff/hg_log_p_template_raw.txt returns None because column-0 prose before the first recognized header (no `changeset:` line, custom template output) latches the dropped-prologue/raw-fallback path. Expected output still contains `[file] bin.dat (binary)` and `[file] f.txt (+1 -1)`.

Common situations: Users with custom hg templates or aliases feeding rtk; Mercurial log output with `--template` used together with `-p`; locale-localized diff markers (same test also covers a French `diff -u` `No newline` marker).

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/89941d829a6dff36. Report an issue: GitHub.

Appendix: source

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

            "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
        // (two streams concatenated): the pair opens its own section
        // rather than renaming the binary one and stealing its note.
        let concat = "diff --git a/x.bin b/x.bin\nindex 1111111..2222222 100644\nBinary files a/x.bin and b/x.bin differ\n--- g1/m.txt\t2026-01-01 00:00:00.000000000 +0000\n+++ g2/m.txt\t2026-01-01 00:00:00.000000000 +0000\n@@ -1 +1 @@\n-x\n+y\n";
        assert_eq!(
            condense_unified_diff_strict(concat).as_deref(),
            Some("[file] x.bin (binary)\n[file] g2/m.txt (+1 -1)\n-x\n+y")

View on GitHub (pinned to 0924356b4c)