{"record":{"id":"cdcb9437963e4063","repo":"gitbutlerapp/gitbutler","slug":"cannot-mix-operations-that-require-materialize-a","errorCode":null,"errorMessage":"cannot mix operations that require `materialize` and `materialize_without_checkout`","messagePattern":"cannot mix operations that require `materialize` and `materialize_without_checkout`","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-transaction/src/lib.rs","lineNumber":969,"sourceCode":"    {\n        let editor = self\n            .inner\n            .rebase\n            .take()\n            .expect(\"rebase is always Some(_)\")\n            .into_editor();\n        let (outcome, materialize_without_checkout, new_rebase) =\n            f(editor, &self.inner.commit_mappings)?;\n\n        match (\n            self.inner.materialize_without_checkout,\n            materialize_without_checkout,\n        ) {\n            (_, MaterializeWithoutCheckout::Either) => {}\n            (MaterializeWithoutCheckout::Either, requested) => {\n                self.inner.materialize_without_checkout = requested;\n            }\n            (demanded, requested) => anyhow::ensure!(\n                demanded == requested,\n                \"cannot mix operations that require `materialize` and `materialize_without_checkout`\"\n            ),\n        }\n\n        self.inner.commit_mappings = CommitMappings(new_rebase.history.commit_mappings());\n        self.inner.rebase = Some(new_rebase);\n        Ok(outcome)\n    }\n}\n\n#[derive(Debug, Clone, Copy, PartialEq, Eq)]\nenum MaterializeWithoutCheckout {\n    Yes,\n    No,\n    Either,\n}\n","sourceCodeStart":951,"sourceCodeEnd":987,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-transaction/src/lib.rs#L951-L987","documentation":"Within one transaction, two operations demanded incompatible materialization modes: one requires a full checkout materialize, another requires materialize_without_checkout (or vice versa). The transaction stores a single mode per run — Either adopts the first concrete request, then any conflicting request trips this ensure!.","triggerScenarios":"Calling e.g. a move_branch (which materializes with checkout) and an amend/squash-style operation (which demands no checkout) through the same transaction instance; any sequence where closures return different MaterializeWithoutCheckout values, both concrete and unequal.","commonSituations":"Batching multiple workspace operations into one transaction for atomicity without checking their materialization requirements; refactoring code that previously ran operations in separate transactions.","solutions":["Split the operations into separate transactions so each uses its own materialization mode","Reorder so all checkout-materializing ops go in one transaction and no-checkout ops in another","If writing a new operation, return MaterializeWithoutCheckout::Either when the op genuinely tolerates both modes"],"exampleFix":"// before — one transaction, conflicting modes\ntx.move_branch(&src, &tgt)?;   // demands materialize (checkout)\ntx.amend_commit(...)?;          // demands materialize_without_checkout → error\n// after — split by mode\ntx.move_branch(&src, &tgt)?;\ntx.commit()?;\nlet mut tx2 = ...; tx2.amend_commit(...)?; tx2.commit()?;","handlingStrategy":"validation","validationCode":"// classify each op's materialization requirement up front and group into transactions\nenum Mode { Checkout, NoCheckout }\nfn plan(ops: &[Op]) -> Vec<Vec<Op>> { /* group ops by Mode; one transaction per group */ }\n// never submit ops of both Modes to a single transaction","typeGuard":"fn compatible(current: MaterializeWithoutCheckout, requested: MaterializeWithoutCheckout) -> bool {\n    matches!(requested, MaterializeWithoutCheckout::Either)\n        || matches!(current, MaterializeWithoutCheckout::Either)\n        || current == requested\n}","tryCatchPattern":"match tx.some_op(...) {\n    Err(e) if e.to_string().contains(\"cannot mix operations\") => {\n        // commit current transaction, open a new one, replay the op there\n    }\n    r => r,\n}","preventionTips":["Document each operation's materialization mode and batch by mode","Prefer MaterializeWithoutCheckout::Either for ops that tolerate both, so they compose freely","Add unit tests that combine ops from both modes and assert the error, to catch regressions early"],"tags":["rust","git","transaction","checkout","api-misuse","but-transaction"],"backgroundTag":"conflicting-transaction-flags","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}