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

  1. Re-apply/verify the rule: an `hg export` changeset header (`# HG changeset patch`) opens a message region that ends at the first file header.
  2. Treat `diff -r ... <file>` echo lines as context markers, not unread noise, when they appear before a header pair.
  3. 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).
  4. 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

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


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)