gitbutlerapp/gitbutler · error
Cannot compute diff specs for branch `{}`
Error message
Cannot compute diff specs for branch `{}` What it means
DiffSpecBuilder::push_changes_id turns a resolved change selection into diff specs, but only handles hunks, path prefixes, committed files, commits, and uncommitted changes. Branch-level selections (CliId::Branch) are supposed to be expanded into files during resolution; if one reaches the builder unexpanded there is no single diff spec for a whole branch, so it bails with the branch name.
Source
Thrown at crates/but/src/utils/diff_specs.rs:71
CliId::UncommittedHunkOrFile(uncommitted) => {
self.push_changes_from_uncommitted(uncommitted)
}
CliId::PathPrefix {
id: _,
hunks,
source: _,
} => self.push_changes_from_path_prefix(hunks),
CliId::CommittedFile {
committed_file:
CommittedFileId {
commit_id,
path,
change_id: _,
},
id: _,
} => self.push_changes_from_committed_file(*commit_id, path.as_ref()),
CliId::Branch(branch) => {
anyhow::bail!("Cannot compute diff specs for branch `{}`", branch.name)
}
CliId::Commit {
commit:
CommitId {
commit_id,
change_id: _,
},
id: _,
} => self.push_changes_from_commit(*commit_id),
CliId::Uncommitted { id: _ } => self.push_changes_from_uncommitted_area(),
// A worktree is expanded into its files during resolution, so the
// builder only ever sees hunks that already come from its own repo.
CliId::Worktree { name, .. } => {
anyhow::bail!("Cannot compute diff specs for worktree `{name}`")
}
CliId::Stack { .. } => {
anyhow::bail!("Cannot compute diff specs for stacks")
}View on GitHub (pinned to caf1f223d3)
Solutions
- Expand the branch into its files/commits during resolution before computing diff specs
- Use a branch-diff API instead of the hunk/file diff-spec builder
- If you maintain the caller, match on CliId and reject Branch early with a clearer message
Example fix
// before
for id in ids {
builder.push_changes_from_id(id)?; // panics-bails on CliId::Branch
}
// after
for id in ids {
if let CliId::Branch(b) = id { return Err(anyhow::anyhow!("expand branch `{}` first", b.name)); }
builder.push_changes_from_id(id)?;
} Defensive patterns
Strategy: type-guard
Type guard
fn supports_diff_specs(id: &CliId) -> bool {
!matches!(id, CliId::Branch { .. })
}
// guard every id before building specs:
assert!(ids.iter().all(|id| supports_diff_specs(id)), "branch ids must be expanded first"); Prevention
- Never hand raw CliId::Branch values to DiffSpecBuilder - expand them during resolution
- Centralize id resolution so the expansion step cannot be skipped
- Add a unit test asserting branch ids are rejected before the builder runs
When it happens
Trigger: Calling the diff-spec computation with a CliId::Branch that skipped the expansion step - a new caller feeding unresolved ids, or a resolution-order change that lets branch ids through to the builder.
Common situations: Programmatic users of the change-selection APIs, refactors that bypass the resolver, new command implementations reusing DiffSpecBuilder directly.
Related errors
- cannot mix mark sources
- cannot mark files from multiple commits
- '{raw_id}' resolved to more than one hunk ({})
- Failed to communicate with LM Studio server: ${error instanc
- When using LM Studio, you must provide a valid endpoint
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/46936165c04d6baa.
Report an issue: GitHub.