GitoxideLabs/gitoxide · error

BUG: hunks are never zero-sized

Error message

BUG: hunks are never zero-sized

What it means

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.

Solutions

  1. Upgrade gix-blame to the latest version where the edge case may be fixed
  2. Reduce the offending repository/commit to a minimal repro and file an upstream issue
  3. As a workaround, blame a specific revision range that avoids the boundary commit

Example fix

// defensive upstream fix
let len = (current_source_range.end - previous_source_range.start) as u32;
let len = NonZeroU32::new(len).expect("BUG: hunks are never zero-sized");
// caller-side: no fix possible; upgrade the crate
Defensive patterns

Strategy: try-catch

Try / catch

let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| gix_blame(...)));
match res { Ok(v) => v, Err(_) => /* fall back to `git blame` CLI */ fallback_blame() }

Prevention

When it happens

Trigger: 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.

Common situations: Edge-case repository histories (empty files, boundary commits) triggering a blame implementation bug; usually reported and fixed in a patch release.

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 GitoxideLabs/gitoxide@e73179060b (2026-09-08). Data as JSON: /api/errors/611cc52bd2a756ea. Report an issue: GitHub.

Appendix: source

Thrown at gix-blame/src/file/function.rs:480

        .into_iter()
        .fold(Vec::with_capacity(len), |mut acc, entry| {
            let previous_entry = acc.last();

            if let Some(previous_entry) = previous_entry {
                let previous_blamed_range = previous_entry.range_in_blamed_file();
                let current_blamed_range = entry.range_in_blamed_file();
                let previous_source_range = previous_entry.range_in_source_file();
                let current_source_range = entry.range_in_source_file();
                if previous_entry.commit_id == entry.commit_id
                    && previous_blamed_range.end == current_blamed_range.start
                    // As of 2024-09-19, the check below only is in `git`, but not in `libgit2`.
                    && previous_source_range.end == current_source_range.start
                {
                    let coalesced_entry = BlameEntry {
                        start_in_blamed_file: previous_blamed_range.start as u32,
                        start_in_source_file: previous_source_range.start as u32,
                        len: NonZeroU32::new((current_source_range.end - previous_source_range.start) as u32)
                            .expect("BUG: hunks are never zero-sized"),
                        commit_id: previous_entry.commit_id,
                        source_file_name: previous_entry.source_file_name.clone(),
                    };

                    acc.pop();
                    acc.push(coalesced_entry);
                } else {
                    acc.push(entry);
                }

                acc
            } else {
                acc.push(entry);

                acc
            }
        })
}

View on GitHub (pinned to e73179060b)