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
- Give every claim line at least one valid hunk range: "path/to/file.rs:<start>-<end>" (comma-separate multiple ranges).
- Check the range syntax uses '-' between numeric bounds — '..' and '_' do not parse as Hunks.
- Regenerate the claims by re-assigning hunks in the app rather than hand-writing them, so Display/FromStr stay symmetric.
- 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
- Always serialize ownership claims with Display (path + ':' + ranges) instead of hand-writing strings.
- Round-trip test: parse(claim.to_string()) must succeed for every claim you write.
- When importing legacy data, drop or repair path-only lines before FromStr parsing.
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
- Missing stack '{stack_id}' for head '{name}'
- but_meta::Stack::derived_name: Stack is uninitialized
- Read-only metadata can't prune branch stack order references
- Cannot import TOML into DB: TOML does not exist or is invali
- line {line_number}: pick does not accept a message clause
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/54f23fab47734aba.
Report an issue: GitHub.