gitbutlerapp/gitbutler · critical

commit id mismatch: failed to recreate a commit from its par

Error message

commit id mismatch: failed to recreate a commit from its parts

What it means

Snapshot restore recreates commits missing from the object database out of their recorded parts (tree, parents, message, author/committer). The rebuilt object must hash exactly to the commit id the snapshot recorded; a mismatch means the stored parts do not reproduce the original commit — corrupt snapshot data or a commit-serialization difference (headers, signatures, encoding) between the writing and restoring versions.

Source

Thrown at crates/gitbutler-oplog/src/oplog.rs:1006

        let Some(commits_tree_entry) = commits_tree_entry else {
            continue;
        };
        let commits_tree = repo
            .find_tree(commits_tree_entry.id())
            .context("failed to convert commits tree entry to tree")?;

        // walk through all the commits in the branch
        for commit_entry in commits_tree.iter() {
            let commit_entry = commit_entry?;
            // for each commit, recreate the commit from the commit data if it doesn't exist
            let commit_id = commit_entry.filename();
            // check for the oid in the repo
            let commit_oid = gix::ObjectId::from_hex(commit_id)?;
            if !repo.has_object(commit_oid) {
                // commit is not in the repo, let's build it from our data
                let new_commit_oid = deserialize_commit(commit_entry.id())?;
                if new_commit_oid != commit_oid {
                    bail!("commit id mismatch: failed to recreate a commit from its parts");
                }
            }

            // TODO: in the next iteration, this of course can't be hardcoded.
            if branch_name == "workspace" {
                restored_workspace_commit = Some(commit_oid);
            }
        }
    }
    if let Some(checkout) = restored_checkout.as_ref() {
        if !repo.has_object(checkout.commit_id) {
            bail!(
                "snapshot checkout commit {} is unavailable",
                checkout.commit_id
            );
        }
        if checkout.ref_name.as_ref() == workspace_ref
            && restored_workspace_commit != Some(checkout.commit_id)

View on GitHub (pinned to caf1f223d3)

Solutions

  1. Restore from an earlier snapshot entry — the oplog usually has many
  2. Make the commits re-fetchable: if the branch was pushed, fetch it so recreation is skipped entirely
  3. Use the same GitButler version that wrote the snapshot, then retry
  4. Report with the snapshot id — a deterministic hash mismatch on restore is a bug candidate
Defensive patterns

Strategy: fallback

Validate before calling

for entry in commits_tree.iter() {
    let oid = gix::ObjectId::from_hex(entry.filename())?;
    if !repo.has_object(oid) && deserialize_commit(entry.id())? != oid {
        return Err(anyhow::anyhow!("snapshot cannot rebuild commit {oid}; pick another snapshot"));
    }
}

Try / catch

match oplog.restore(target_snapshot_id) {
    Err(err) if err.to_string().contains("commit id mismatch") => {
        // walk back to the previous oplog entry and restore that instead
    }
    other => other,
}

Prevention

When it happens

Trigger: Restoring a snapshot that references commits absent from the repository, where deserializing the stored commit data yields a different oid than the entry's recorded filename.

Common situations: Restoring snapshots across GitButler versions whose commit serialization changed; partially corrupted oplog trees; snapshot blobs rewritten or truncated.

Related errors


AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20). Data as JSON: /api/errors/5f974602c9b99573. Report an issue: GitHub.