gitbutlerapp/gitbutler · error
Cannot operate on uncommitted changes in worktree
Error message
Cannot operate on uncommitted changes in worktree {name} yet What it means
The diff-specs builder was constructed to read one change source (by default the main worktree), but was pushed an `UncommittedChanges` whose source is a different worktree. Since hunks from different worktrees address different files, the builder refuses rather than silently operating on the wrong files; operations that legitimately support worktree sources must use `DiffSpecsBuilder::for_change_source`.
Solutions
- Run the operation with the worktree as the active context, or use the worktree-aware construction path (`for_change_source`) if the operation supports worktree sources
- Operate on the main worktree's uncommitted changes instead
- Move/commit the changes in the linked worktree first, then reference the resulting commit
Defensive patterns
Strategy: validation
Validate before calling
if let Some(name) = uncommitted.source.worktree_name() {
if builder_source != uncommitted.source {
eprintln!("uncommitted changes belong to worktree '{name}'; run the operation there or use a worktree-capable builder");
return Ok(());
}
} Type guard
fn source_matches(b: &DiffSpecsBuilder, u: &UncommittedChanges) -> bool { b.source == u.source } Try / catch
match result { Err(e) if e.to_string().contains("Cannot operate on uncommitted changes in worktree") => { /* re-run inside the worktree */ }, other => other } Prevention
- Construct the builder with `for_change_source` when worktree sources must be supported
- Only feed uncommitted changes whose source equals the builder's source
- Run worktree-scoped operations from within that worktree
When it happens
Trigger: Calling `push_changes_from_uncommitted` (directly or via `push_changes_from_id` with an uncommitted id) where `uncommitted.source != self.source` and the incoming source is a linked worktree; the current builder was not created with `for_change_source`.
Common situations: Targeting uncommitted changes that live in a linked worktree with a command that only supports the main worktree; passing a worktree's uncommitted id to a main-worktree builder.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- `but setup` cannot run from a linked worktree; run it from…
- Cannot compute diff specs for worktree
- No uncommitted changes to discard
- Repository installers require the main worktree
- The repository at is a non-main worktree. GitButler…
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/9bf854c404bcc377.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but/src/utils/diff_specs.rs:112
}
CliId::CommittedHunk(_) => {
anyhow::bail!("Cannot compute diff specs for committed hunks")
}
}
}
pub fn push_changes_from_uncommitted(
&mut self,
uncommitted: &UncommittedHunkOrFile,
) -> anyhow::Result<()> {
// Specs are built against one checkout's repository, so a hunk from
// another would silently address the wrong files. Operations that support
// worktree sources validate the selection and construct the builder via
// [`Self::for_change_source`]; the rest read the main worktree and refuse
// worktree hunks here.
if uncommitted.source != self.source {
if let Some(name) = uncommitted.source.worktree_name() {
anyhow::bail!("Cannot operate on uncommitted changes in worktree {name} yet");
}
anyhow::bail!(
"BUG: a change from {} was pushed into a builder reading {}",
uncommitted.source.describe(),
self.source.describe()
);
}
let hunks = uncommitted.hunks.iter().cloned();
self.push_hunks(hunks.map(|id_and_hunk| id_and_hunk.hunk))
}
pub fn push_changes_from_path_prefix(
&mut self,
hunks: &nonempty::NonEmpty<IdAndHunk>,
) -> anyhow::Result<()> {
self.push_hunks(hunks.iter().map(|id_and_hunk| id_and_hunk.hunk.clone()))
}
View on GitHub (pinned to 58e5313667)