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

  1. Remove any step whose commit_id equals the rebase base commit before submitting the plan.
  2. Set the base to the parent of the intended picked commit so the picked commit is rewritten, not used as the base.
  3. 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

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


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)