{"record":{"id":"5ffede1d5ef864d7","repo":"gitbutlerapp/gitbutler","slug":"ad-hoc-single-branch-branch-moves-are-not-suppor","errorCode":null,"errorMessage":"Ad-hoc (single-branch) branch moves are not supported inside transactions","messagePattern":"Ad-hoc \\(single-branch\\) branch moves are not supported inside transactions","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/but-transaction/src/lib.rs","lineNumber":457,"sourceCode":"    /// field is ever set rather than silently dropping a metadata reorder or a required checkout.\n    ///\n    /// [`Outcome::new_tip`]: but_workspace::branch::move_branch::Outcome::new_tip\n    /// [`Outcome::branch_stack_order`]: but_workspace::branch::move_branch::Outcome::branch_stack_order\n    pub fn stack_branch_on(\n        &mut self,\n        source_branch: &FullNameRef,\n        target_branch: &FullNameRef,\n    ) -> anyhow::Result<()> {\n        let (ws_meta, new_tip, branch_stack_order) = self.rebase(|editor, _| {\n            let outcome = but_workspace::branch::move_branch(editor, source_branch, target_branch)?;\n            Ok((\n                (outcome.ws_meta, outcome.new_tip, outcome.branch_stack_order),\n                MaterializeWithoutCheckout::No,\n                outcome.rebase,\n            ))\n        })?;\n\n        anyhow::ensure!(\n            new_tip.is_none() && branch_stack_order.is_none(),\n            \"Ad-hoc (single-branch) branch moves are not supported inside transactions\"\n        );\n\n        self.record_workspace_metadata_update(ws_meta)?;\n\n        Ok(())\n    }\n\n    pub fn tear_off_branch(&mut self, source_branch: &FullNameRef) -> anyhow::Result<()> {\n        let ws_meta = self.rebase(|editor, _| {\n            let outcome = but_workspace::branch::tear_off_branch(editor, source_branch, None)?;\n            Ok((\n                outcome.ws_meta,\n                MaterializeWithoutCheckout::No,\n                outcome.rebase,\n            ))\n        })?;","sourceCodeStart":439,"sourceCodeEnd":475,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/caf1f223d3cfb94488c9198ad34487c6006c648f/crates/but-transaction/src/lib.rs#L439-L475","documentation":"The transactional wrapper around branch moves asserts that the underlying but_workspace::branch::move_branch produced no new_tip/branch_stack_order. If it did, the move was an ad-hoc single-branch move (a legacy, non-transactional shape), which transactions cannot record atomically. The ensure! fires to refuse rather than half-apply.","triggerScenarios":"Calling the transaction-based move_branch(source, target) where the workspace operation resolves to a single-branch (ad-hoc) move instead of a full stack move — e.g. moving a branch whose stack contains other managed branches such that only one branch relocates.","commonSituations":"UI or API code migrated from the ad-hoc move API onto the transaction API without restricting it to whole-stack moves; reordering branches in a multi-branch workspace via the wrong entry point.","solutions":["Use the non-transactional single-branch move API for ad-hoc moves","Use the transaction API only for operations that move entire stacks and return no per-branch tip/order changes","If you maintain the workspace layer, return the stack-shaped outcome (no new_tip/branch_stack_order) for transactional callers"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"// before the transactional call, confirm the move is stack-shaped, not ad-hoc\nlet outcome_preview = but_workspace::branch::move_branch(&editor, &src, &tgt)?;\nif outcome_preview.new_tip.is_some() || outcome_preview.branch_stack_order.is_some() {\n    return Err(anyhow!(\"ad-hoc move — use the non-transactional API\"));\n}","typeGuard":"fn is_stack_move(outcome: &MoveBranchOutcome) -> bool {\n    outcome.new_tip.is_none() && outcome.branch_stack_order.is_none()\n}","tryCatchPattern":"match tx.move_branch(&src, &tgt) {\n    Err(e) if e.to_string().contains(\"Ad-hoc\") => { /* route to non-transactional move API */ }\n    other => other,\n}","preventionTips":["Match the API to the operation: whole-stack moves through transactions, single-branch moves through the ad-hoc API","Watch for regression tests covering both move shapes when refactoring branch move code"],"tags":["rust","git","virtual-branches","transaction","api-misuse","but-transaction"],"backgroundTag":"unsupported-operation-in-transaction","analyzedSha":"caf1f223d3cfb94488c9198ad34487c6006c648f","analyzedAt":"2026-08-20T07:55:40.983Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}