GitoxideLabs/gitoxide · error

BUG: hunks are never empty

Error message

BUG: hunks are never empty

What it means

Same invariant as force_non_zero: when constructing a Hunk, the library expects range_in_blamed_file to have non-zero length, because a blame hunk by definition covers at least one line. A zero-length range at construction time panics via expect instead of returning an error.

Solutions

  1. Ensure ranges passed to Hunk::new contain at least one line before calling
  2. Validate range length at the call site and skip empty ranges
  3. Return a Result/Option from Hunk::new instead of panicking if empty ranges are legitimate in your use case
  4. Add a debug assertion or validation upstream where ranges are computed

Example fix

// before
let hunk = Hunk::new(range_in_blamed_file, range_in_source_file, commit_id, None);
// after
assert!(!range_in_blamed_file.is_empty(), "hunks must cover at least one line");
let hunk = Hunk::new(range_in_blamed_file, range_in_source_file, commit_id, None);
Defensive patterns

Strategy: validation

Validate before calling

if range_in_blamed_file.is_empty() {
    return Err(anyhow!("hunk range must contain at least one line"));
}

Type guard

fn non_empty_range(r: &Range<usize>) -> bool { !r.is_empty() }

Prevention

When it happens

Trigger: Hunk::new (public constructor) called with a range_in_blamed_file whose len() is 0 — e.g. empty or start==end ranges passed by callers or produced by a faulty diff.

Common situations: Users or internal code constructing hunks from empty line ranges, typically after a diff computed no lines for a region or after off-by-one range math.

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/c5f0184b1bbe83d0. Report an issue: GitHub.

Appendix: source

Thrown at gix-blame/src/types.rs:391

        range_in_blamed_file: Range<u32>,
        range_in_source_file: Range<u32>,
        commit_id: ObjectId,
        source_file_name: Option<BString>,
    ) -> Self {
        debug_assert!(
            range_in_blamed_file.end > range_in_blamed_file.start,
            "{range_in_blamed_file:?}"
        );
        debug_assert!(
            range_in_source_file.end > range_in_source_file.start,
            "{range_in_source_file:?}"
        );
        debug_assert_eq!(range_in_source_file.len(), range_in_blamed_file.len());

        Self {
            start_in_blamed_file: range_in_blamed_file.start,
            start_in_source_file: range_in_source_file.start,
            len: NonZeroU32::new(range_in_blamed_file.len() as u32).expect("BUG: hunks are never empty"),
            commit_id,
            source_file_name,
        }
    }
}

impl BlameEntry {
    /// Return the range of tokens this entry spans in the *Blamed File*.
    pub fn range_in_blamed_file(&self) -> Range<usize> {
        let start = self.start_in_blamed_file as usize;
        start..start + self.len.get() as usize
    }
    /// Return the range of tokens this entry spans in the *Source File*.
    pub fn range_in_source_file(&self) -> Range<usize> {
        let start = self.start_in_source_file as usize;
        start..start + self.len.get() as usize
    }
}

View on GitHub (pinned to e73179060b)