{"record":{"id":"1098767c832a7398","repo":"gitbutlerapp/gitbutler","slug":"fixup-commit-must-not-come-after-a-reference-step","errorCode":null,"errorMessage":"Fixup commit must not come after a reference step","messagePattern":"Fixup commit must not come after a reference step","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-rebase/src/lib.rs","lineNumber":188,"sourceCode":"    ///\n    /// Fixup operations:\n    /// - Must not be a reference step immediately before it\n    /// - Must not be the first operation\n    ///\n    /// Reference operations:\n    /// - The refname must be a valid reference name\n    fn validate_step(&self, step: &RebaseStep) -> Result<()> {\n        match step {\n            RebaseStep::Pick { commit_id, .. } => {\n                self.assure_unique_step_and_existing_non_base(commit_id, \"Picked\")?;\n            }\n            RebaseStep::SquashIntoPreceding {\n                commit_id,\n                new_message: _,\n            } => {\n                self.assure_unique_step_and_existing_non_base(commit_id, \"Fixup\")?;\n                if matches!(self.steps.last(), Some(RebaseStep::Reference { .. })) {\n                    bail!(\"Fixup commit must not come after a reference step\");\n                }\n                if self.steps.is_empty() {\n                    bail!(\"Fixup must have a commit to work on\");\n                }\n            }\n            RebaseStep::Reference(name) => {\n                if matches!(name, but_core::Reference::Virtual(name) if name.is_empty()) {\n                    return Err(anyhow!(\n                        \"Reference step must have a non-empty virtual branch name\"\n                    ));\n                }\n            }\n        }\n        Ok(())\n    }\n\n    fn assure_unique_step_and_existing_non_base(\n        &self,","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-rebase/src/lib.rs#L170-L206","documentation":"Rebase plans are validated step by step. A SquashIntoPreceding (fixup) step squashes its commit into the immediately preceding step, so the preceding step must be a commit-bearing pick. If the last recorded step is a Reference step, there is no preceding pick to squash into, and validation rejects the plan.","triggerScenarios":"Calling a step-adding API (e.g. squash_into_preceding) when self.steps.last() is Some(RebaseStep::Reference(..)) — i.e. a reference was registered immediately before the fixup.","commonSituations":"Building plans programmatically where reference steps and fixups are interleaved; UI-generated plans that place a reference right before a squash; reordering operations that moved a reference after the target commit.","solutions":["Reorder so the fixup follows the pick it should squash into, not a reference.","Insert the reference after the fixup step instead of before it.","Drop the reference step if it is not needed at that position."],"exampleFix":"// before\nrebase.reference(\"my-branch\")?;\nrebase.squash_into_preceding(commit_id, None)?; // fails\n// after\nrebase.squash_into_preceding(commit_id, None)?;\nrebase.reference(\"my-branch\")?;","handlingStrategy":"validation","validationCode":"fn can_squash(steps: &[RebaseStep]) -> bool {\n    matches!(steps.last(), Some(RebaseStep::Pick(_)))\n}","typeGuard":"if !matches!(rebase.last_step(), Some(RebaseStep::Pick(_))) { return Err(anyhow!(\"fixup requires a preceding pick\")); }","tryCatchPattern":null,"preventionTips":["Only emit squash_into_preceding directly after a pick step","Emit reference steps after fixups when generating plans","Unit-test plan generators for reference/fixup ordering"],"tags":["rebase","validation","step-order"],"backgroundTag":"invalid-state-transition","analyzedSha":"58e5313667b857ef39a730e380af31816a7b1768","analyzedAt":"2026-09-18T06:50:32.052Z","contentChangedAt":"2026-09-18T06:50:32.052Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}