rtk-ai/rtk · error
hg export must parse
Error message
hg export must parse
What it means
`.expect("hg export must parse")` panic: `condense_unified_diff_strict()` returned None on an `hg export` fixture. `hg export` emits a changeset header, a column-0 commit message, then a `diff -r <a> -r <b> <file>` echo before each header pair; the prose used to latch `dropped_prologue` and the echo forced a whole-stream raw fallback (0% savings for all `hg export` output). The fix under test: the changeset header opens a message region like an mbox `From`.
Solutions
- Re-apply/verify the rule: an `hg export` changeset header (`# HG changeset patch`) opens a message region that ends at the first file header.
- Treat `diff -r ... <file>` echo lines as context markers, not unread noise, when they appear before a header pair.
- Ensure after the first file header the second echo still marks `diff -r` context so genuinely unread column-0 lines later force raw (per the test's follow-up).
- Regenerate the fixture if hg's export format changed in the recorded version.
Example fix
// before
if line.starts_with("diff -r ") { dropped_prologue = true; } // whole-stream raw
// after
if line.starts_with("# HG changeset patch") { open_message_region(); }
if line.starts_with("diff -r ") { mark_diff_context(); } Defensive patterns
Strategy: validation
Validate before calling
let out = condense_unified_diff_strict(&hg_export_fixture);
assert!(out.as_deref().map_or(false, |o| o.contains("[file] f.txt (+1 -1)") && o.contains("[file] g.txt (+1 -0)") && !o.contains("bullet"))); Type guard
fn parses_cleanly(diff: &str) -> bool {
crate::cmds::git::diff_cmd::condense_unified_diff_strict(diff)
.map_or(false, |o| !o.is_empty())
} Try / catch
let out = condense_unified_diff_strict(hg_export_output)
.unwrap_or_else(|| hg_export_output.to_string()); Prevention
- Model producer prologues (mbox From, hg changeset) with one shared message-region mechanism; do not special-case per flag.
- Treat producer echo lines (`diff -r ...`) as context, never as fatal unread noise.
- Verify savings do not drop to 0% for a whole producer: 0% is the symptom of an over-eager raw fallback.
- Add one fixture per producer echo format (hg export, hg log -p, format-patch).
When it happens
Trigger: Calling `condense_unified_diff_strict()` on tests/fixtures/diff/hg_export_raw.txt returns None, or returns output missing `[file] f.txt (+1 -1)` / `[file] g.txt (+1 -0)`, or leaking message prose (`bullet`).
Common situations: Running `rtk hg export`; changes to the message-region state machine shared with mbox handling; regressions where the second `diff -r` echo (outside the message region) re-triggers raw fallback on a later column-0 line.
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
- templated hg log must parse
- noise, not raw
- --numstat -p must parse
- hg diff must parse
- hg log -p must parse
AI-assisted analysis of rtk-ai/rtk@0924356b4c (2026-09-19).
Data as JSON: /api/errors/88bd1679b2481c59.
Report an issue: GitHub.
Appendix: source
Thrown at src/cmds/git/diff_cmd.rs:6735
);
assert_eq!(
condense_unified_diff(fixture),
fixture,
"{name}: not byte-exact"
);
}
}
#[test]
fn hg_export_condenses_despite_its_prologue_and_diff_echo() {
// Real `hg export tip` (Mercurial 7.0.1): `# …` headers and the
// message at column 0, then a `diff -r <a> -r <b> <file>` echo before
// each header pair. The prose used to latch `dropped_prologue`, and
// the echo then turned it into a whole-stream raw fallback: 0%
// savings for a whole producer. The changeset header now opens a
// message region, like an mbox `From`.
let fixture = include_str!("../../../tests/fixtures/diff/hg_export_raw.txt");
let out = condense_unified_diff_strict(fixture).expect("hg export must parse");
assert!(out.contains("[file] f.txt (+1 -1)"), "got:\n{out}");
assert!(out.contains("[file] g.txt (+1 -0)"), "got:\n{out}");
assert!(!out.contains("bullet"), "message prose leaked:\n{out}");
// The message region ends at the first file header; the second
// echo arrives outside it and still marks the `diff -r` context, so
// an unread column-0 line after it forces raw as in any GNU stream.
let with_fact = format!("{fixture}Seulement dans b: h.txt\n");
assert!(condense_unified_diff_strict(&with_fact).is_none());
}
#[test]
fn no_newline_marker_stays_attached_to_the_line_it_describes() {
// Real `git diff` of two files: `f.txt` changes its first line while
// its unchanged last line lacks a newline (marker after a context
// line), and `g.txt` gains a trailing newline (marker after `-y`).
// The first marker used to land under `+A` and claim the ADDED line
// had no newline; it must be dropped. The second is the witness of
// a newline-only change and must stay right under `-y`.View on GitHub (pinned to 0924356b4c)