GitoxideLabs/gitoxide · error
BUG: cannot call commit() before prepare(…)
Error message
BUG: cannot call commit() before prepare(…)
What it means
Panic raised when `packed::Transaction::commit()` is called without a preceding `prepare(...)`. `self.edits` is an `Option` populated only by `prepare`, so `expect` proves the caller skipped the required phase. This is a deliberate API-misuse guard, analogous to the loose-ref transaction guard.
Solutions
- Call `transaction.prepare(...)` (with a lock context) before `transaction.commit()`.
- Ensure the prepare step's Result is checked — a failed prepare must abort, not fall through to commit.
- Restructure so prepare and commit are always paired in the same code path.
Example fix
// before let tx = store.packed_transaction(lock, gix_ref::transaction::PackedRefs::DeletionsAndNonSymbolicUpdates)?; tx.commit()?; // after let mut tx = store.packed_transaction(lock, gix_ref::transaction::PackedRefs::DeletionsAndNonSymbolicUpdates)?; tx.prepare(edits, &objects, forward_or_reverse)?; tx.commit()?;
Defensive patterns
Strategy: type-guard
Validate before calling
// always prepare a packed transaction before committing let mut tx = store.packed_transaction(lock, packed_refs_opt)?; tx.prepare(edits, &objects, /*move_to_top*/ true)?; tx.commit()?;
Type guard
fn commit_packed(mut tx: gix_ref::transaction::PackedTransaction, edits: Vec<RefEdit>, objects: &dyn gix_object::Find) -> Result<(), gix_ref::store::packed::transaction::commit::Error> {
tx.prepare(edits, objects, true)?;
tx.commit()
} Try / catch
// panics are not Result errors; enforce phase order by construction
let mut tx = ...;
if !tx.is_prepared() { tx.prepare(edits, &objects, true)?; }
tx.commit()?; Prevention
- Never call commit() on a freshly created packed transaction.
- Abort the whole flow if prepare() returns Err.
- Centralize packed-ref edits in one helper that pairs prepare/commit.
When it happens
Trigger: Creating a `gix_ref::store::packed::Transaction` via `Store::packed_transaction()` or `File::transaction()`, then calling `.commit()` directly instead of `.prepare(...)` first.
Common situations: Code that conditionally prepares but unconditionally commits; refactors moving prepare into another function; misunderstandings of the two-phase packed-refs API.
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: must call prepare before commit
- a write lock for applying changes
- this one only happens on iteration creation
- parser validation
- peeked value exists
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/726682314199c2c2.
Report an issue: GitHub.
Appendix: source
Thrown at gix-ref/src/store/packed/transaction.rs:158
.take()
.map(gix_lock::File::close)
.transpose()
.map_err(prepare::Error::CloseLock)?;
} else {
// NOTE that we don't do any additional checks here but apply all edits unconditionally.
// This is because this transaction system is internal and will be used correctly from the
// loose ref store transactions, which do the necessary checking.
}
self.edits = Some(edits);
Ok(self)
}
/// Commit the prepared transaction.
///
/// Please note that actual edits invalidated existing packed buffers.
/// Note: There is the potential to write changes into memory and return such a packed-refs buffer for reuse.
pub fn commit(self) -> Result<(), commit::Error> {
let mut edits = self.edits.expect("BUG: cannot call commit() before prepare(…)");
if edits.is_empty() {
return Ok(());
}
let mut file = self.lock.expect("a write lock for applying changes");
let refs_sorted: Box<dyn Iterator<Item = Result<packed::Reference<'_>, packed::iter::Error>>> =
match self.buffer.as_ref() {
Some(buffer) => Box::new(buffer.iter()?),
None => Box::new(std::iter::empty()),
};
let mut refs_sorted = refs_sorted.peekable();
edits.sort_by(|l, r| l.inner.name.as_bstr().cmp(r.inner.name.as_bstr()));
let mut peekable_sorted_edits = edits.iter().peekable();
file.with_mut(|f| f.write_all(HEADER_LINE))?;
View on GitHub (pinned to e73179060b)