gitbutlerapp/gitbutler · error
commit cannot be the base commit
Error message
{kind} commit cannot be the base commit What it means
but-rebase's `assure_unique_step_and_existing_non_base` validates a Pick or Fixup/Squash step before a rebase plan runs. If the commit referenced by the step is identical to the rebase's base commit (the commit the new history is built on top of), the operation is rejected: you cannot pick or fixup a commit onto itself as the base. This prevents a degenerate plan where a step would rewrite the base commit that is supposed to remain fixed.
Solutions
- Remove any step whose commit_id equals the rebase base commit before submitting the plan.
- Set the base to the parent of the intended picked commit so the picked commit is rewritten, not used as the base.
- Deduplicate/rebuild the step list from the actual base selection to avoid including the base commit as a step.
Example fix
// before
let request = RebaseRequest::new(repo, base)
.pick(picked) // picked == base -> error
.build();
// after
let steps: Vec<RebaseStep> = all_selected
.into_iter()
.filter(|c| c != base)
.map(|c| RebaseStep::Pick { commit_id: c, new_message: None })
.collect();
let request = RebaseRequest::new(repo, base).steps(steps).build(); Defensive patterns
Strategy: validation
Validate before calling
fn step_is_valid_base(step_commit: &gix::oid, base: Option<&gix::oid>) -> bool {
Some(step_commit) != base
}
// filter before building the plan:
let steps: Vec<_> = candidates.into_iter().filter(|c| step_is_valid_base(c, base.as_ref())).collect(); Type guard
fn is_not_base(commit_id: &gix::oid, base: &Option<gix::ObjectId>) -> bool {
!matches!(base, Some(b) if b == commit_id)
} Try / catch
match request.validate_step(&step) {
Err(e) if e.to_string().contains("cannot be the base commit") => {
// drop the offending step and rebuild the plan
}
other => other?,
} Prevention
- Always derive the step list from a selection that excludes the base commit.
- Validate every step with validate_step before executing the plan.
- Recompute steps whenever the base selection changes.
When it happens
Trigger: Calling a validation path (via `validate_step`) with a `RebaseStep::Pick { commit_id }` or `RebaseStep::SquashIntoPreceding { commit_id }` whose `commit_id` equals `RebaseRequest.base` (compared as `Some(commit_id) == self.base`). The commit must otherwise exist in the repo (`find_commit` succeeds) for the check to be reached.
Common situations: Building a rebase plan programmatically from UI selections where the base commit and a picked commit come from the same selection list; off-by-one logic that includes the base commit in the picked list; stale cached commit IDs after the base was changed but the steps weren't recomputed.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Picked commit already exists in a previous step
- Aborting due to empty branch name
- Base commit must exist if provided
- Branch name ' ' collides with existing branch
- Can only check out local branches under refs/heads, got
AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18).
Data as JSON: /api/errors/0793c9f0122dd784.
Report an issue: GitHub.
Appendix: source
Thrown at crates/but-rebase/src/lib.rs:212
RebaseStep::Reference(name) => {
if matches!(name, but_core::Reference::Virtual(name) if name.is_empty()) {
return Err(anyhow!(
"Reference step must have a non-empty virtual branch name"
));
}
}
}
Ok(())
}
fn assure_unique_step_and_existing_non_base(
&self,
commit_id: &gix::oid,
kind: &str,
) -> Result<()> {
self.repo.find_commit(commit_id)?;
if Some(commit_id) == self.base.as_deref() {
bail!("{kind} commit cannot be the base commit");
}
if self.steps.iter().any(|s| s.commit_id() == Some(commit_id)) {
bail!("Picked commit already exists in a previous step");
}
Ok(())
}
}
#[instrument(level = "debug", skip(repo))]
fn rebase(
repo: &gix::Repository,
base: Option<gix::ObjectId>,
base_substitute: Option<gix::ObjectId>,
steps: Vec<RebaseStep>,
pick_mode: PickMode,
) -> Result<RebaseOutput> {
let (mut references, mut commit_mapping) = (
vec![],View on GitHub (pinned to 58e5313667)