{"record":{"id":"611cc52bd2a756ea","repo":"GitoxideLabs/gitoxide","slug":"bug-hunks-are-never-zero-sized","errorCode":null,"errorMessage":"BUG: hunks are never zero-sized","messagePattern":"BUG: hunks are never zero-sized","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-blame/src/file/function.rs","lineNumber":480,"sourceCode":"        .into_iter()\n        .fold(Vec::with_capacity(len), |mut acc, entry| {\n            let previous_entry = acc.last();\n\n            if let Some(previous_entry) = previous_entry {\n                let previous_blamed_range = previous_entry.range_in_blamed_file();\n                let current_blamed_range = entry.range_in_blamed_file();\n                let previous_source_range = previous_entry.range_in_source_file();\n                let current_source_range = entry.range_in_source_file();\n                if previous_entry.commit_id == entry.commit_id\n                    && previous_blamed_range.end == current_blamed_range.start\n                    // As of 2024-09-19, the check below only is in `git`, but not in `libgit2`.\n                    && previous_source_range.end == current_source_range.start\n                {\n                    let coalesced_entry = BlameEntry {\n                        start_in_blamed_file: previous_blamed_range.start as u32,\n                        start_in_source_file: previous_source_range.start as u32,\n                        len: NonZeroU32::new((current_source_range.end - previous_source_range.start) as u32)\n                            .expect(\"BUG: hunks are never zero-sized\"),\n                        commit_id: previous_entry.commit_id,\n                        source_file_name: previous_entry.source_file_name.clone(),\n                    };\n\n                    acc.pop();\n                    acc.push(coalesced_entry);\n                } else {\n                    acc.push(entry);\n                }\n\n                acc\n            } else {\n                acc.push(entry);\n\n                acc\n            }\n        })\n}","sourceCodeStart":462,"sourceCodeEnd":498,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-blame/src/file/function.rs#L462-L498","documentation":"When coalescing adjacent blame hunks in `gix-blame`, the merged length is computed as `NonZeroU32::new(end - start).expect(\"BUG: hunks are never zero-sized\")`. The invariant is that every tracked hunk spans at least one line, so the difference is positive; a zero would mean empty hunks were recorded earlier in the algorithm.","triggerScenarios":"Running blame (public `BlameEntry`-producing flow) on inputs where adjacent hunks have `current_source_range.end == previous_source_range.start`, i.e. a zero-length range slipping through hunk computation — an internal algorithm bug rather than bad user input.","commonSituations":"Edge-case repository histories (empty files, boundary commits) triggering a blame implementation bug; usually reported and fixed in a patch release.","solutions":["Upgrade gix-blame to the latest version where the edge case may be fixed","Reduce the offending repository/commit to a minimal repro and file an upstream issue","As a workaround, blame a specific revision range that avoids the boundary commit"],"exampleFix":"// defensive upstream fix\nlet len = (current_source_range.end - previous_source_range.start) as u32;\nlet len = NonZeroU32::new(len).expect(\"BUG: hunks are never zero-sized\");\n// caller-side: no fix possible; upgrade the crate","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| gix_blame(...)));\nmatch res { Ok(v) => v, Err(_) => /* fall back to `git blame` CLI */ fallback_blame() }","preventionTips":["Pin recent gix-blame versions","Test blame on boundary/empty-file commits in CI","Keep a git CLI fallback for blame in tooling"],"tags":["rust","panic","blame","internal-invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"e73179060badf27222d790981fac3f84c1830a7e","analyzedAt":"2026-09-08T11:26:50.865Z","contentChangedAt":"2026-09-08T11:26:50.865Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}