gitbutlerapp/gitbutler · error
Can't squash if previous commit is missing
Error message
Can't squash if previous commit is missing
What it means
A `RebaseStep::SquashIntoPreceding` step squashes its commit into the commit produced by the preceding step, tracked via `cursor`. If `cursor` is `None` when the squash step executes, there is no preceding rewritten commit to squash into, so `rebase` bails out. This is a plan-shape invariant: a squash step must follow a step that produced a commit.
Solutions
- Ensure at least one Pick (or a valid base) precedes every SquashIntoPreceding step.
- Run `validate_step` on each step when building the plan so this shape error is caught before execution.
- Convert a leading squash step into a plain Pick with the desired message instead.
Example fix
// before steps = [squash_into_preceding(c1), pick(c2)]; // no preceding commit // after steps = [pick(c2), squash_into_preceding(c1)];
Defensive patterns
Strategy: validation
Validate before calling
fn squash_steps_have_preceding_pick(steps: &[RebaseStep]) -> bool {
let mut saw_pick = false;
for s in steps {
match s {
RebaseStep::Pick { .. } => saw_pick = true,
RebaseStep::SquashIntoPreceding { .. } if !saw_pick => return false,
_ => {}
}
}
true
} Type guard
fn can_squash(base: Option<gix::ObjectId>, prior_steps: &[RebaseStep]) -> bool {
base.is_some() || prior_steps.iter().any(|s| matches!(s, RebaseStep::Pick { .. }))
} Try / catch
match rebase(repo, base, base_substitute, steps, pick_mode) {
Err(e) if e.to_string().contains("previous commit is missing") => {
// insert a Pick step before the squash, or convert the squash into a Pick
}
other => other,
} Prevention
- Keep every SquashIntoPreceding step after at least one Pick step.
- Run validate_step during plan construction to catch this before execution.
- Guard UI reorder logic so fixup/squash rows cannot be moved above the first pick.
When it happens
Trigger: Executing a rebase whose step list contains `RebaseStep::SquashIntoPreceding` while `cursor` is still `None` — i.e. the squash is the first step, or all preceding steps failed to establish a cursor (crates/but-rebase/src/lib.rs:317).
Common situations: Fixup/squash-first reorderings produced by UI drag-and-drop; plan construction that puts a squash step before any Pick; note that `validate_step` guards `Fixup` steps with "Fixup must have a commit to work on", so this error typically arises from plans not routed through full validation.
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
- Cannot currently rebase a commit so that it becomes the…
- A matching parent can't be found in the output graph
- A parent in the output graph is not a pick
- An edge with desired order
- Attempted to update the symbolic reference
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/4b58dd3fd3df2d89.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-rebase/src/lib.rs:317
SignCommit::IfSignCommitsEnabled,
None,
)?);
}
None => {
// TODO: should this be supported? This would be as easy as forgetting its parents.
bail!(
"Cannot currently rebase a commit so that it becomes the first commit in the history"
)
}
}
}
}
RebaseStep::SquashIntoPreceding {
commit_id,
new_message,
} => {
let Some(cursor) = &mut cursor else {
bail!("Can't squash if previous commit is missing");
};
last_seen_commit = Some(commit_id);
let base_commit = repo.find_commit(*cursor)?;
let new_commit = cherry_pick_one(
repo,
*cursor,
commit_id,
PickMode::Unconditionally,
EmptyCommit::Keep,
)?;
// Now, lets pretend the base didn't exist by swapping parent with the parent of the base
let mut new_commit = repo.find_commit(new_commit)?.decode()?.to_owned()?;
new_commit.parents = base_commit.parent_ids().map(|id| id.detach()).collect();
if let Some(new_message) = new_message {
new_commit.message =
but_core::commit::rewrite_conflict_markers_on_message_change(
new_commit.message.as_ref(),View on GitHub (pinned to 58e5313667)