gitbutlerapp/gitbutler · error · anyhow::Error
cannot mix operations that require `materialize` and `materi
Error message
cannot mix operations that require `materialize` and `materialize_without_checkout`
What it means
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!.
Source
Thrown at crates/but-transaction/src/lib.rs:969
{
let editor = self
.inner
.rebase
.take()
.expect("rebase is always Some(_)")
.into_editor();
let (outcome, materialize_without_checkout, new_rebase) =
f(editor, &self.inner.commit_mappings)?;
match (
self.inner.materialize_without_checkout,
materialize_without_checkout,
) {
(_, MaterializeWithoutCheckout::Either) => {}
(MaterializeWithoutCheckout::Either, requested) => {
self.inner.materialize_without_checkout = requested;
}
(demanded, requested) => anyhow::ensure!(
demanded == requested,
"cannot mix operations that require `materialize` and `materialize_without_checkout`"
),
}
self.inner.commit_mappings = CommitMappings(new_rebase.history.commit_mappings());
self.inner.rebase = Some(new_rebase);
Ok(outcome)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MaterializeWithoutCheckout {
Yes,
No,
Either,
}
View on GitHub (pinned to caf1f223d3)
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
Example fix
// before — one transaction, conflicting modes tx.move_branch(&src, &tgt)?; // demands materialize (checkout) tx.amend_commit(...)?; // demands materialize_without_checkout → error // after — split by mode tx.move_branch(&src, &tgt)?; tx.commit()?; let mut tx2 = ...; tx2.amend_commit(...)?; tx2.commit()?;
Defensive patterns
Strategy: validation
Validate before calling
// classify each op's materialization requirement up front and group into transactions
enum Mode { Checkout, NoCheckout }
fn plan(ops: &[Op]) -> Vec<Vec<Op>> { /* group ops by Mode; one transaction per group */ }
// never submit ops of both Modes to a single transaction Type guard
fn compatible(current: MaterializeWithoutCheckout, requested: MaterializeWithoutCheckout) -> bool {
matches!(requested, MaterializeWithoutCheckout::Either)
|| matches!(current, MaterializeWithoutCheckout::Either)
|| current == requested
} Try / catch
match tx.some_op(...) {
Err(e) if e.to_string().contains("cannot mix operations") => {
// commit current transaction, open a new one, replay the op there
}
r => r,
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Batching multiple workspace operations into one transaction for atomicity without checking their materialization requirements; refactoring code that previously ran operations in separate transactions.
Related errors
- Ad-hoc (single-branch) branch moves are not supported inside
- Cannot position reference below unborn segment '{}'
- Failed to communicate with LM Studio server: ${error instanc
- Invalid response: ${JSON.stringify(result)}
- When using GitButler's API to summarize code, you must be lo
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/cdcb9437963e4063.
Report an issue: GitHub.