GitoxideLabs/gitoxide · error
a write lock for applying changes
Error message
a write lock for applying changes
What it means
Panic raised by `self.lock.expect("a write lock for applying changes")` inside `packed::Transaction::commit()`. The write lock is acquired during `prepare`; its absence at commit time means commit was called without a successful prepare, or the lock was already consumed. It is an internal consistency check on the two-phase transaction lifecycle.
Solutions
- Check the `Result` returned by `prepare()` and abort on error instead of calling `commit()`.
- Always `prepare()` (which acquires the lock) before `commit()`.
- Ensure only one code path owns the transaction and its lock.
Example fix
// before let mut tx = store.packed_transaction(lock, opts)?; let _ = tx.prepare(edits, &objects, true); // error ignored tx.commit()?; // after let mut tx = store.packed_transaction(lock, opts)?; tx.prepare(edits, &objects, true)?; // aborts on failure tx.commit()?;
Defensive patterns
Strategy: type-guard
Validate before calling
// prepare() acquires the lock; verify it succeeded before commit
tx.prepare(edits, &objects, true).map_err(|e| format!("prepare failed: {e}"))?;
tx.commit()?; Type guard
fn ensure_prepared_and_commit(tx: gix_ref::transaction::PackedTransaction, r: Result<(), PrepareErr>) -> Result<(), Box<dyn std::error::Error>> {
r?; // propagate prepare failure; do not fall through to commit
tx.commit().map_err(Into::into)
} Try / catch
// treat prepare error as terminal
match tx.prepare(edits, &objects, true) {
Ok(()) => tx.commit()?,
Err(e) => return Err(e.into()),
} Prevention
- Always check prepare()'s Result before calling commit().
- Ensure the packed-refs lock isn't released or consumed elsewhere.
- Avoid sharing a transaction across threads or retry loops that re-enter commit.
When it happens
Trigger: Calling `packed::Transaction::commit()` when `prepare()` was never called, failed to acquire the lock, or the transaction's lock was taken elsewhere — i.e. the transaction is not in the prepared state.
Common situations: Ignoring the `Result` of `prepare()` and proceeding to commit; concurrent code paths where another component consumed or released the packed-refs lock; refactored two-phase flows.
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(…)
- this one only happens on iteration creation
- parser validation
- peeked value exists
- name retrieval configured
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/bf8c0892475e3e05.
Report an issue: GitHub.
Appendix: source
Thrown at gix-ref/src/store/packed/transaction.rs:163
// 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))?;
let mut num_written_lines = 0;
loop {
match (refs_sorted.peek(), peekable_sorted_edits.peek()) {
(Some(Err(_)), _) => {
let err = refs_sorted.next().expect("next").expect_err("err");View on GitHub (pinned to e73179060b)