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
- Restore from an earlier snapshot entry — the oplog usually has many
- Make the commits re-fetchable: if the branch was pushed, fetch it so recreation is skipped entirely
- Use the same GitButler version that wrote the snapshot, then retry
- 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
- Push branches so commits exist in the ODB and never need rebuilding
- Don't switch GitButler versions in the middle of an undo chain
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
- targetRef in project_meta.toml is not a remote-tracking bran
- targetCommitId in project_meta.toml is null
- Invalid conflict stage '{}'
- snapshot checkout ref is not a local branch
- snapshot checkout commit is null
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/5f974602c9b99573.
Report an issue: GitHub.