{"record":{"id":"186e423f62399b76","repo":"GitoxideLabs/gitoxide","slug":"bug-must-call-prepare-before-commit","errorCode":null,"errorMessage":"BUG: must call prepare before commit","messagePattern":"BUG: must call prepare before commit","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"gix-ref/src/store/file/transaction/commit.rs","lineNumber":33,"sourceCode":"    /// On error the transaction may have been performed partially, depending on the nature of the error, and no attempt to roll back\n    /// partial changes is made.\n    ///\n    /// In this stage, we perform the following operations:\n    ///\n    /// * update the ref log\n    /// * move updated refs into place\n    /// * delete reflogs and empty parent directories\n    /// * delete packed refs\n    /// * delete their corresponding reference (if applicable)\n    ///   along with empty parent directories\n    ///\n    /// Note that transactions will be prepared automatically as needed.\n    pub fn commit<'a>(self, committer: impl Into<Option<gix_actor::SignatureRef<'a>>>) -> Result<Vec<RefEdit>, Error> {\n        self.commit_inner(committer.into())\n    }\n\n    fn commit_inner(self, committer: Option<gix_actor::SignatureRef<'_>>) -> Result<Vec<RefEdit>, Error> {\n        let mut updates = self.updates.expect(\"BUG: must call prepare before commit\");\n        let delete_loose_refs = matches!(\n            self.packed_refs,\n            PackedRefs::DeletionsAndNonSymbolicUpdatesRemoveLooseSourceReference(_)\n        );\n\n        // Perform updates first so live commits remain referenced\n        for change in &mut updates {\n            assert!(!change.update.deref, \"Deref mode is turned into splits and turned off\");\n            match &change.update.change {\n                // reflog first, then reference\n                Change::Update { log, new, expected } => {\n                    let lock = change.lock.take();\n                    let (update_ref, update_reflog) = match log.mode {\n                        RefLog::Only => (false, true),\n                        RefLog::AndReference => (true, true),\n                    };\n                    if update_reflog {\n                        let log_update = match new {","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/GitoxideLabs/gitoxide/blob/e73179060badf27222d790981fac3f84c1830a7e/gix-ref/src/store/file/transaction/commit.rs#L15-L51","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet tx = store.transaction();\nlet edits = tx.commit(Some(committer))?;\n// after\nlet mut tx = store.transaction();\ntx.prepare(edit_vec, None, /*start_over*/ false)?;\nlet edits = tx.commit(Some(committer))?;","handlingStrategy":"type-guard","validationCode":"// ensure the transaction is in the prepared state before committing\nlet tx = store.transaction();\ntx.prepare(edits, None, false)?; // prepare is required\ntx.commit(committer)?;","typeGuard":"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> {\n    // commit() itself panics unless prepare() ran; always pair them\n    tx.commit(c)\n}","tryCatchPattern":"// panics are not catchable results; ensure pairing statically\nlet mut tx = store.transaction();\ntx.prepare(edits, packed_refs, false).map_err(|e| ...)?;\nlet edits = tx.commit(committer).map_err(|e| ...)?;","preventionTips":["Always call prepare() immediately after creating a ref transaction.","Never ignore the Result of prepare().","Keep prepare and commit in the same function scope."],"tags":["panic","transaction","ref-transaction","api-misuse"],"backgroundTag":"invalid-state-transition","analyzedSha":"e73179060badf27222d790981fac3f84c1830a7e","analyzedAt":"2026-09-08T11:26:50.865Z","contentChangedAt":"2026-09-08T11:26:50.865Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}