{"record":{"id":"9bf854c404bcc377","repo":"gitbutlerapp/gitbutler","slug":"cannot-operate-on-uncommitted-changes-in-worktree","errorCode":null,"errorMessage":"Cannot operate on uncommitted changes in worktree {name} yet","messagePattern":"Cannot operate on uncommitted changes in worktree (.+?) yet","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but/src/utils/diff_specs.rs","lineNumber":104,"sourceCode":"            }\n            CliId::Stack { .. } => {\n                anyhow::bail!(\"Cannot compute diff specs for stacks\")\n            }\n        }\n    }\n\n    pub fn push_changes_from_uncommitted(\n        &mut self,\n        uncommitted: &UncommittedHunkOrFile,\n    ) -> anyhow::Result<()> {\n        // Specs are built against one checkout's repository, so a hunk from\n        // another would silently address the wrong files. Operations that support\n        // worktree sources validate the selection and construct the builder via\n        // [`Self::for_change_source`]; the rest read the main worktree and refuse\n        // worktree hunks here.\n        if uncommitted.source != self.source {\n            if let Some(name) = uncommitted.source.worktree_name() {\n                anyhow::bail!(\"Cannot operate on uncommitted changes in worktree {name} yet\");\n            }\n            anyhow::bail!(\n                \"BUG: a change from {} was pushed into a builder reading {}\",\n                uncommitted.source.describe(),\n                self.source.describe()\n            );\n        }\n        let hunks = uncommitted.hunks.iter().cloned();\n        self.push_hunks(hunks.map(|id_and_hunk| id_and_hunk.hunk))\n    }\n\n    pub fn push_changes_from_path_prefix(\n        &mut self,\n        hunks: &nonempty::NonEmpty<IdAndHunk>,\n    ) -> anyhow::Result<()> {\n        self.push_hunks(hunks.iter().map(|id_and_hunk| id_and_hunk.hunk.clone()))\n    }\n","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but/src/utils/diff_specs.rs#L86-L122","documentation":"Diff specs are built against one checkout's repository, so `push_changes_from_uncommitted` refuses `UncommittedHunkOrFile` values whose `source` differs from the builder's `source`. When the mismatched hunks come from a linked worktree you get this 'yet' message; the operation either needs a builder created for that worktree or does not support worktree sources. (A non-worktree mismatch produces the internal BUG variant instead.)","triggerScenarios":"Passing uncommitted hunks captured in worktree `name` into a builder constructed for the main worktree (the default constructor) rather than via `DiffSpecBuilder::for_change_source` with the selection's own source.","commonSituations":"Multi-worktree projects where selections mix hunks from different checkouts; new operations reusing a main-worktree builder for arbitrary selections; UI flows that forward worktree hunks to main-worktree-only operations.","solutions":["Construct the builder with `DiffSpecBuilder::for_change_source(...)` using the selection's source so it reads the correct worktree","Filter the selection so it only contains hunks whose source matches the operation's expected source","If the operation is intentionally main-worktree-only, reject worktree selections at the UI/CLI boundary before invoking it"],"exampleFix":"// before\nlet mut builder = DiffSpecBuilder::new(&main_repo); // reads the main worktree\nbuilder.push_changes_from_uncommitted(&hunk_from_worktree)?;\n\n// after\nlet mut builder = DiffSpecBuilder::for_change_source(&hunk_from_worktree.source, ...)?;\nbuilder.push_changes_from_uncommitted(&hunk_from_worktree)?;","handlingStrategy":"validation","validationCode":"// verify sources agree before pushing uncommitted hunks\nif uncommitted.source != builder.source {\n    if let Some(name) = uncommitted.source.worktree_name() {\n        return Err(anyhow::anyhow!(\"operation does not support worktree `{name}`\"));\n    }\n    return Err(anyhow::anyhow!(\"selection source does not match the builder\"));\n}","typeGuard":"fn matches_builder_source(uncommitted: &UncommittedHunkOrFile, builder: &DiffSpecBuilder<'_>) -> bool {\n    uncommitted.source == builder.source\n}","tryCatchPattern":"if let Err(err) = builder.push_changes_from_uncommitted(&uncommitted) {\n    if err.to_string().contains(\"worktree\") {\n        let mut builder = DiffSpecBuilder::for_change_source(&uncommitted.source, /* ... */)?;\n        builder.push_changes_from_uncommitted(&uncommitted)?;\n    } else {\n        return Err(err);\n    }\n}","preventionTips":["Construct builders from the selection's ChangeSource, never from a fixed repository","Validate selections against the operation's supported sources at the API boundary"],"tags":["rust","gitbutler","worktree","diff-specs","source-mismatch"],"backgroundTag":"wrong-worktree-context","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","schemaVersion":2},"datasetVersion":"2026-08-23T21:17:23.414Z"}