gitbutlerapp/gitbutler · error

Cannot compute diff specs for worktree `{name}`

Error message

Cannot compute diff specs for worktree `{name}`

What it means

Thrown by the diff-spec builder in the `but` crate when a change selection identifying a whole worktree (`CliId::Worktree`) reaches `push_changes`. By design, worktree selections are expanded into their individual files during selection resolution, so the builder only ever expects hunks that already come from its own repository. Seeing this error means a caller passed an unresolved worktree id straight to the builder, bypassing the resolution step.

Source

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

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

    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() {

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Route the selection through the standard resolution step that expands worktrees into file-level selections before calling push_changes
  2. If the operation supports worktree sources, validate the selection and construct the builder via `DiffSpecBuilder::for_change_source` instead of the main-worktree constructor
  3. If you never call the builder directly, report a bug against the command that produced the unresolved worktree selection

Example fix

// before
let mut builder = DiffSpecBuilder::new(&repo);
builder.push_changes(&cli_id)?; // cli_id is CliId::Worktree

// after: expand aggregate selections during resolution first
let resolved = resolve_selection(&repo, &cli_id)?; // worktree -> its files/hunks
let mut builder = DiffSpecBuilder::new(&repo);
builder.push_changes(&resolved)?;
Defensive patterns

Strategy: validation

Validate before calling

// before computing diff specs, expand aggregate selections
if matches!(cli_id, CliId::Worktree { .. } | CliId::Stack { .. }) {
    let cli_id = resolve_selection(&repo, &cli_id)?; // expands worktree/stack into files
    // ... 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 worktree") => {
        // selection was not resolved; expand it and retry
    }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: Calling `DiffSpecBuilder::push_changes` (or a wrapper around it) with a `CliId::Worktree { name, .. }` value instead of the resolved per-file/per-hunk selections, e.g. a new CLI command or TUI flow that forwards raw CLI selections directly into diff-spec computation.

Common situations: New commands that accept arbitrary `CliId` values but forget to run the resolver that expands worktrees into files; refactors that route selections around the resolution step; unit tests that hand raw ids to the builder.

Related errors


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