{"record":{"id":"89941d829a6dff36","repo":"rtk-ai/rtk","slug":"templated-hg-log-must-parse","errorCode":null,"errorMessage":"templated hg log must parse","messagePattern":"templated hg log must parse","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/cmds/git/diff_cmd.rs","lineNumber":6617,"sourceCode":"            \"got:\\n{out}\"\n        );\n        // `--submodule=diff`: nested column-0 sections under the range.\n        let sd = include_str!(\"../../../tests/fixtures/diff/git_diff_submodule_diff_raw.txt\");\n        assert_eq!(\n            condense_unified_diff_strict(sd).as_deref(),\n            Some(\n                \"[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)\"\n            )\n        );\n        // `git log --numstat -p`: `-<TAB>-<TAB>b.bin` before the diff.\n        let ns = include_str!(\"../../../tests/fixtures/diff/git_log_numstat_p_raw.txt\");\n        let out = condense_unified_diff_strict(ns).expect(\"--numstat -p must parse\");\n        assert!(out.contains(\"[file] b.bin (binary)\"), \"got:\\n{out}\");\n        assert!(out.contains(\"[file] d/e/f (+1 -0)\"), \"got:\\n{out}\");\n        // `hg log -p --template '{node|short} {desc}\\n'`: a prose prologue\n        // with no `changeset:` line, then hg's echo — prose is not a fact.\n        let tmpl = include_str!(\"../../../tests/fixtures/diff/hg_log_p_template_raw.txt\");\n        let out = condense_unified_diff_strict(tmpl).expect(\"templated hg log must parse\");\n        assert!(out.contains(\"[file] bin.dat (binary)\"), \"got:\\n{out}\");\n        assert!(out.contains(\"[file] f.txt (+1 -1)\"), \"got:\\n{out}\");\n        // `LC_ALL=fr_FR.UTF-8 diff -u`: the marker is localized, position\n        // decides — kept after `-x` and after `+y`.\n        let fr = include_str!(\"../../../tests/fixtures/diff/diff_u_fr_no_newline_raw.txt\");\n        assert_eq!(\n            condense_unified_diff_strict(fr).as_deref(),\n            Some(\n                \"[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\"\n            )\n        );\n        // A git binary section followed by another producer's header pair\n        // (two streams concatenated): the pair opens its own section\n        // rather than renaming the binary one and stealing its note.\n        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\";\n        assert_eq!(\n            condense_unified_diff_strict(concat).as_deref(),\n            Some(\"[file] x.bin (binary)\\n[file] g2/m.txt (+1 -1)\\n-x\\n+y\")","sourceCodeStart":6599,"sourceCodeEnd":6635,"githubUrl":"https://github.com/rtk-ai/rtk/blob/0924356b4caba4989607227b7c8824d3d8098719/src/cmds/git/diff_cmd.rs#L6599-L6635","documentation":"`.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.","triggerScenarios":"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)`.","commonSituations":"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).","solutions":["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.","Only force raw fallback when unread column-0 noise appears after a diff context starts, not before the first header.","Regenerate the fixture from the exact hg version/template if the recorded echo format drifted.","Add a fixture-less unit test: prose + patches must condense."],"exampleFix":"// before\nif !saw_changeset_header { dropped_prologue = true; } // later => raw fallback\n// after\nif !saw_changeset_header { open_message_region(); } // prose skipped, patches still condensed","handlingStrategy":"validation","validationCode":"assert!(condense_unified_diff_strict(&hg_template_fixture)\n    .map_or(false, |o| o.contains(\"[file] f.txt (+1 -1)\")), \"templated prologue must not force raw\");","typeGuard":"fn strictly_parses(diff: &str) -> bool { crate::cmds::git::diff_cmd::condense_unified_diff_strict(diff).is_some() }","tryCatchPattern":"let out = condense_unified_diff_strict(hg_output).unwrap_or_else(|| hg_output.to_string());\n// production callers never panic: condense_stdin already does this (diff_cmd.rs:498)","preventionTips":["Treat any column-0 prose before the first header as a skippable message region, not a fatal condition.","Only force raw fallback when noise appears after diff context has started.","Add fixtures for custom hg templates if users are known to use them with rtk.","Keep marker-localization (LC_ALL) cases in the same test module as prose cases."],"tags":["rust","test-failure","diff-parsing","mercurial"],"backgroundTag":"internal-invariant-violation","analyzedSha":"0924356b4caba4989607227b7c8824d3d8098719","analyzedAt":"2026-09-19T19:25:29.612Z","contentChangedAt":"2026-09-19T19:25:29.612Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}