GitoxideLabs/gitoxide · error
BUG: must call prepare before commit
Error message
BUG: must call prepare before commit
What it means
Panic raised when `file::Transaction::commit()` is called on a reference transaction that was never `prepare()`d. `commit_inner` unwraps `self.updates`, which is `Option::take`-cleared/consumed during `prepare`; `None` proves the prepare step was skipped. The API contract requires `prepare()` before `commit()`, though docs note prepare happens automatically in normal flows.
Solutions
- Call `transaction.prepare(edits_or_packed_refs)` before `transaction.commit(committer)`.
- If you don't need two-phase control, use the flow that prepares automatically as documented.
- Restructure code so the transaction isn't committed after its prepare state was consumed.
Example fix
// before let tx = store.transaction(); let edits = tx.commit(Some(committer))?; // after let mut tx = store.transaction(); tx.prepare(edit_vec, None, /*start_over*/ false)?; let edits = tx.commit(Some(committer))?;
Defensive patterns
Strategy: type-guard
Validate before calling
// ensure the transaction is in the prepared state before committing let tx = store.transaction(); tx.prepare(edits, None, false)?; // prepare is required tx.commit(committer)?;
Type guard
fn commit_prepared(tx: gix_ref::transaction::FileTransaction, c: Option<gix_actor::SignatureRef<'_>>) -> Result<Vec<gix_ref::gix_fmt::RefEdit>, gix_ref::transaction::commit::Error> {
// commit() itself panics unless prepare() ran; always pair them
tx.commit(c)
} Try / catch
// panics are not catchable results; ensure pairing statically let mut tx = store.transaction(); tx.prepare(edits, packed_refs, false).map_err(|e| ...)?; let edits = tx.commit(committer).map_err(|e| ...)?;
Prevention
- Always call prepare() immediately after creating a ref transaction.
- Never ignore the Result of prepare().
- Keep prepare and commit in the same function scope.
When it happens
Trigger: Constructing a `gix_ref::store::file::Transaction` (via `Store::transaction()`), then calling `.commit(...)` directly without first calling `.prepare(...)` — only possible when bypassing the auto-prepare flow or holding the transaction across custom update edits.
Common situations: Custom ref-update code that adds edits after prepare, refactors that split prepare/commit incorrectly, or upgrading gix-ref and relying on an old flow that allowed bare commits.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- BUG: cannot call commit() before prepare(…)
- never without parent
- a write lock for applying changes
- user error: multiple calls are allowed only until it…
- ' ' is not a valid configuration key
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/186e423f62399b76.
Report an issue: GitHub.
Appendix: source
Thrown at gix-ref/src/store/file/transaction/commit.rs:33
/// On error the transaction may have been performed partially, depending on the nature of the error, and no attempt to roll back
/// partial changes is made.
///
/// In this stage, we perform the following operations:
///
/// * update the ref log
/// * move updated refs into place
/// * delete reflogs and empty parent directories
/// * delete packed refs
/// * delete their corresponding reference (if applicable)
/// along with empty parent directories
///
/// Note that transactions will be prepared automatically as needed.
pub fn commit<'a>(self, committer: impl Into<Option<gix_actor::SignatureRef<'a>>>) -> Result<Vec<RefEdit>, Error> {
self.commit_inner(committer.into())
}
fn commit_inner(self, committer: Option<gix_actor::SignatureRef<'_>>) -> Result<Vec<RefEdit>, Error> {
let mut updates = self.updates.expect("BUG: must call prepare before commit");
let delete_loose_refs = matches!(
self.packed_refs,
PackedRefs::DeletionsAndNonSymbolicUpdatesRemoveLooseSourceReference(_)
);
// Perform updates first so live commits remain referenced
for change in &mut updates {
assert!(!change.update.deref, "Deref mode is turned into splits and turned off");
match &change.update.change {
// reflog first, then reference
Change::Update { log, new, expected } => {
let lock = change.lock.take();
let (update_ref, update_reflog) = match log.mode {
RefLog::Only => (false, true),
RefLog::AndReference => (true, true),
};
if update_reflog {
let log_update = match new {View on GitHub (pinned to e73179060b)