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

  1. Check the `Result` returned by `prepare()` and abort on error instead of calling `commit()`.
  2. Always `prepare()` (which acquires the lock) before `commit()`.
  3. 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

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


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)