gitbutlerapp/gitbutler · error

Found multiple matches for

Error message

Found multiple matches for {commit_id}

What it means

When resolving a commit identifier against indexed stacks, the lookup expects at most one commit to match. If the iterator yields two or more commits with the same id across stacks/segments, the result is ambiguous and this error is thrown rather than guessing.

Solutions

  1. Run `but status` to see which stacks contain the commit; disambiguate by operating on the specific stack/branch instead of the bare id.
  2. Remove or de-duplicate the duplicated commit (unapply from one stack, or cherry-pick cleanup) and retry.
  3. Rebuild/refresh the workspace index so stale duplicate entries are cleared.
Defensive patterns

Strategy: try-catch

Validate before calling

// before resolving, inspect stacks for the id
let count = workspace.iter().filter(|c| c.commit_id() == commit_id).count();
if count > 1 { eprintln!("commit {} is duplicated across stacks; disambiguate first", commit_id); }

Try / catch

match workspace.find_commit(commit_id) {
    Err(e) if e.to_string().contains("Found multiple matches") => {
        // pick the stack-scoped lookup instead
        let c = stack.find_commit(commit_id)?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: Looking up a commit id that appears in more than one stack or in multiple segments of the indexed workspace — e.g., the same commit was pushed/applied to two stacks, or duplicated by a workspace rebuild — so `matches.next()` returns `Some` twice.

Common situations: A cherry-pick or duplicate apply placed the same commit in two branches; stale workspace index containing overlapping stack entries; referring to a short id that collides across stacks.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@58e5313667 (2026-09-18). Data as JSON: /api/errors/2c202a07d39c7ca0. Report an issue: GitHub.

Appendix: source

Thrown at crates/but/src/id/mod.rs:1803

    /// Returns all known stacks.
    pub fn stacks(&self) -> &Vec<StackWithId> {
        self.indexed_stacks.borrow_owner()
    }

    /// Get a distinct commit by ID.
    ///
    /// Errors if the commit cannot be found or if there are multiple matches.
    pub fn get_commit_by_id(&self, commit_id: gix::ObjectId) -> anyhow::Result<CommitWithId<'_>> {
        let mut matches = self
            .commits()
            .filter(|commit_with_id| match commit_with_id {
                CommitWithId::Local(commit) => commit.commit_id() == commit_id,
                CommitWithId::Remote(commit) => commit.commit_id() == commit_id,
            });

        match (matches.next(), matches.next()) {
            (Some(commit), None) => Ok(commit),
            (Some(_), Some(_)) => Err(anyhow::anyhow!("Found multiple matches for {commit_id}")),
            _ => Err(anyhow::anyhow!("Could not find commit {commit_id}")),
        }
    }

    fn commits(&self) -> impl Iterator<Item = CommitWithId<'_>> {
        let stack_commits = self.indexed_stacks.borrow_owner().iter().flat_map(|stack| {
            stack.segments.iter().flat_map(|segment| {
                segment
                    .workspace_commits
                    .iter()
                    .map(CommitWithId::Local)
                    .chain(segment.remote_commits.iter().map(CommitWithId::Remote))
            })
        });

        stack_commits.chain(
            // Commits owned by a linked worktree resolve exactly like workspace commits - they
            // just live outside the stacks.

View on GitHub (pinned to 58e5313667)