{"record":{"id":"9a83e30f53fb2382","repo":"gitbutlerapp/gitbutler","slug":"fixup-must-have-a-commit-to-work-on","errorCode":null,"errorMessage":"Fixup must have a commit to work on","messagePattern":"Fixup must have a commit to work on","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/but-rebase/src/lib.rs","lineNumber":191,"sourceCode":"    /// - 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,\n        commit_id: &gix::oid,\n        kind: &str,\n    ) -> Result<()> {","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/gitbutlerapp/gitbutler/blob/58e5313667b857ef39a730e380af31816a7b1768/crates/but-rebase/src/lib.rs#L173-L209","documentation":"A SquashIntoPreceding (fixup) step requires a preceding step to squash into. If self.steps is empty when the fixup is validated, there is no commit to fix up into, so validation rejects the plan. Note this check runs after the reference check, and assure_unique_step_and_existing_non_base already verified the fixup commit itself exists and is unique.","triggerScenarios":"Calling squash_into_preceding (or building a plan whose first step is a fixup) on a Rebase with no prior steps — e.g. steps empty because base-only construction or prior steps were never added.","commonSituations":"Starting a plan with a fixup by mistake; logic that clears steps before re-adding them while fixups are queued; UI automation emitting a squash as the first action of a new rebase.","solutions":["Add the target pick step before the fixup: call a step-adding method (e.g. push/commit step) first.","If the fixup should start the plan, convert it into a normal pick instead of squash_into_preceding.","Check plan-generation code so fixups are only emitted after their target commit step."],"exampleFix":"// before\nrebase.squash_into_preceding(fixup_id, None)?; // empty plan -> fails\n// after\nrebase.push(base_commit_id)?;\nrebase.squash_into_preceding(fixup_id, None)?;","handlingStrategy":"validation","validationCode":"fn has_step_to_fixup(steps: &[RebaseStep]) -> bool {\n    !steps.is_empty()\n}","typeGuard":"if steps.is_empty() { return Err(anyhow!(\"fixup requires a prior step\")); }","tryCatchPattern":null,"preventionTips":["Never start a plan with a fixup step","Assert plan non-emptiness before appending fixups","In plan generators, always emit the target pick before its fixups"],"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"}