gitbutlerapp/gitbutler · error

Cannot compute diff specs for stacks

Error message

Cannot compute diff specs for stacks

What it means

Thrown by the diff-spec builder in the `but` crate when a change selection identifying a whole stack (`CliId::Stack`) reaches `push_changes`. Stacks are aggregate selections that must be expanded into their constituent commits/hunks during resolution; the builder only accepts selections resolvable against one repository checkout. Hitting this bail means an unexpanded stack id was passed directly.

Source

Thrown at crates/but/src/utils/diff_specs.rs:88

            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")
            }
        }
    }

    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!(

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Run the selection through the standard resolution step that expands stacks into commit-level selections before building specs
  2. Use the validated operation path that supports the selection kind instead of calling the builder directly
  3. If reached via an existing command, report a bug with the exact selection you passed

Example fix

// before
let mut builder = DiffSpecBuilder::new(&repo);
builder.push_changes(&CliId::Stack { .. })?;

// after: expand the stack into its commits first
let resolved = resolve_selection(&repo, &cli_id)?; // stack -> its commits
let mut builder = DiffSpecBuilder::new(&repo);
builder.push_changes(&resolved)?;
Defensive patterns

Strategy: validation

Validate before calling

if matches!(cli_id, CliId::Stack { .. }) {
    let cli_id = resolve_selection(&repo, &cli_id)?; // expands the stack into commits
    // ... proceed with the resolved selection
}

Type guard

fn is_pushable_diff_spec_source(id: &CliId) -> bool {
    !matches!(id, CliId::Worktree { .. } | CliId::Stack { .. })
}

Try / catch

match builder.push_changes(&selection) {
    Ok(()) => { /* continue */ }
    Err(err) if err.to_string().contains("Cannot compute diff specs for stacks") => {
        // expand the stack selection and retry
    }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: Calling `DiffSpecBuilder::push_changes` with a `CliId::Stack { .. }` value, bypassing the resolution step that flattens a stack into per-commit selections.

Common situations: CLI/TUI commands that accept stack selections for operations that only compute specs from commits or uncommitted hunks; refactors that skip resolution; tests passing raw ids.

Related errors


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