gitbutlerapp/gitbutler · error

ownership ranges cannot be empty

Error message

ownership ranges cannot be empty

What it means

Thrown by OwnershipClaim::from_str in crates/but-meta/src/virtual_branches_legacy_types.rs when parsing a BranchOwnershipClaims line: the code splits the value on ':' from the right and tries to parse each segment as a comma-separated list of Hunk ranges; if no segment parses, ranges stays empty and the claim is rejected because an ownership claim must own at least one hunk range. The expected line format is a file path followed by one or more ':'-separated hunk specs, e.g. "src/main.rs:1-10,20-30".

Source

Thrown at crates/but-meta/src/virtual_branches_legacy_types.rs:335

        fn from_str(value: &str) -> std::result::Result<Self, Self::Err> {
            let mut file_path_parts = vec![];
            let mut ranges = vec![];
            for part in value.split(':').rev() {
                match part
                    .split(',')
                    .map(str::parse)
                    .collect::<anyhow::Result<Vec<Hunk>>>()
                {
                    Ok(rr) => ranges.extend(rr),
                    Err(_) => {
                        file_path_parts.insert(0, part);
                    }
                }
            }

            if ranges.is_empty() {
                Err(anyhow::anyhow!("ownership ranges cannot be empty"))
            } else {
                Ok(Self {
                    file_path: file_path_parts
                        .join(":")
                        .parse()
                        .context(format!("failed to parse file path from {value}"))?,
                    hunks: ranges.clone(),
                })
            }
        }
    }

    impl fmt::Display for OwnershipClaim {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
            if self.hunks.is_empty() {
                write!(f, "{}", self.file_path.display())
            } else {
                write!(

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Give every claim line at least one valid hunk range: "path/to/file.rs:<start>-<end>" (comma-separate multiple ranges).
  2. Check the range syntax uses '-' between numeric bounds — '..' and '_' do not parse as Hunks.
  3. Regenerate the claims by re-assigning hunks in the app rather than hand-writing them, so Display/FromStr stay symmetric.
  4. If migrating old data, preprocess path-only lines by appending the full-file range or dropping them before parsing.

Example fix

// before: path-only claim, parses to zero ranges
let claim: anyhow::Result<OwnershipClaim> = "src/main.rs".parse(); // Err: ownership ranges cannot be empty

// after: path plus hunk range
let claim: anyhow::Result<OwnershipClaim> = "src/main.rs:1-42".parse(); // Ok
Defensive patterns

Strategy: validation

Validate before calling

// Validate a claims line before parsing: needs path + at least one N-N range segment
fn claim_line_is_valid(line: &str) -> bool {
    let colon = line.find(':');
    if colon.is_none() { return false; }
    line[colon.unwrap() + 1..]
        .split(',')
        .all(|r| r.split_once('-').map(|(a, b)| a.parse::<u32>().is_ok() && b.parse::<u32>().is_ok()).unwrap_or(false))
}

Type guard

fn has_valid_hunk_range(line: &str) -> bool {
    line.split(':').skip(1).any(|seg| {
        seg.split(',').all(|r| {
            r.split_once('-')
                .map(|(a, b)| a.parse::<u32>().is_ok() && b.parse::<u32>().is_ok())
                .unwrap_or(false)
        })
    })
}

Try / catch

match line.parse::<OwnershipClaim>() {
    Ok(claim) => claims.push(claim),
    Err(e) if e.to_string().contains("ownership ranges cannot be empty") => {
        tracing::warn!("skipping range-less ownership line: {line}"); continue;
    },
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Parsing a claims line like "src/main.rs" with no range segment; ranges written in an unparseable form (e.g. "1_10" or "1..10" instead of "1-10", or non-numeric bounds); a Windows path where every segment fails Hunk::parse so nothing lands in ranges.

Common situations: Hand-edited or machine-generated virtual_branches.json ownership claims missing the hunk suffix; code writing claims with a different separator than Display uses; legacy data from tools that emitted path-only claims.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/54f23fab47734aba. Report an issue: GitHub.