gitbutlerapp/gitbutler · error

Base commit must exist if provided

Error message

Base commit must exist if provided: {base}

What it means

Rebase::new accepts an optional base commit that the rebase builds on top of. If a base ObjectId is supplied, the library verifies the object exists in the repository before constructing the builder; otherwise later steps would fail confusingly. This fail-fast check reports an unknown base immediately.

Solutions

  1. Fetch the missing commit (git fetch origin <sha>) so it exists locally.
  2. Verify the base id with `git cat-file -e <oid>` before calling Rebase::new.
  3. Pass None as the base if no base commit is required, or correct the stale id.

Example fix

// before
Rebase::new(&repo, Some(stale_oid), None, steps)?;
// after
assert!(repo.has_object(stale_oid), "base must exist");
Rebase::new(&repo, Some(stale_oid), None, steps)?;
Defensive patterns

Strategy: validation

Validate before calling

if let Some(base) = base {
    if !repo.has_object(base) {
        return Err(anyhow!("base commit {} missing locally; fetch first", base));
    }
}

Try / catch

match rebase_result {
    Err(e) if e.to_string().starts_with("Base commit must exist") => {
        // fetch the missing oid then retry
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling Rebase::new(repo, base, base_substitute, ...) with Some(oid) where repo.has_object(oid) is false — a commit id not present in the local object database.

Common situations: Passing a commit id from another clone/remote without fetching it; typos or truncated SHAs; referencing a commit that was garbage-collected; stale workspace state after force-push.

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/c54590807f07e6c3. Report an issue: GitHub.

Appendix: source

Thrown at crates/but-rebase/src/lib.rs:101

    /// that all other commits should be placed on top of.
    /// If `None` this means the first picked commit will have no parents.
    /// This means that the first [picked commit](Self::steps()) will be placed right on top of `base`.
    ///
    /// If the first pick refers to a merge-commit then we will have to prove it's connected to the `base` commit.
    /// If that `base`, however, is also a new commit, we'd have no way of figuring out which parent in the picked merge
    /// is replaced with `base` to know which commits are involved in the merge.
    /// The `base_substitute` passed here is the commit that stands in for `base` in the original graph that the
    /// picked merge commit is linked to.
    pub fn new(
        repo: &'repo gix::Repository,
        base: impl Into<Option<gix::ObjectId>>,
        base_substitute: Option<gix::ObjectId>,
    ) -> Result<Self> {
        let base = base.into();
        if let Some(base) = base
            && !repo.has_object(base)
        {
            bail!("Base commit must exist if provided: {base}");
        }
        Ok(Self {
            repo,
            base,
            base_substitute,
            steps: Vec::new(),
            rebase_noops: true, // default to always rebasing
        })
    }

    /// Adds and validates a list of rebase steps.
    /// Ordered oldest (parentmost) to newest (childmost). Reference steps refer to the commit that precedes them.
    /// Note that `steps` will extend whatever steps were added before.
    pub fn steps(&mut self, steps: impl IntoIterator<Item = RebaseStep>) -> Result<&mut Self> {
        for step in steps {
            self.validate_step(&step)?;
            self.steps.push(step);
        }

View on GitHub (pinned to 58e5313667)