gitbutlerapp/gitbutler · error
Commit is not a snapshot
Error message
Commit is not a snapshot
What it means
OplogExt::get_snapshot (crates/gitbutler-oplog/src/oplog.rs) treats a commit as an oplog snapshot only if its raw commit message parses into SnapshotDetails (via SnapshotDetails::from_str). If the message is not valid UTF-8 or does not parse, the commit is rejected with this error. Not every commit in the repository is an oplog entry — only those created by the oplog with serialized SnapshotDetails in the message.
Source
Thrown at crates/gitbutler-oplog/src/oplog.rs:266
) -> Result<gix::ObjectId> {
let PreparedSnapshot {
tree_id,
target_base_oid,
} = prepare_snapshot_with_target(self, perm.read_permission())?;
let repo = self.repo.get()?;
commit_snapshot(self, &repo, tree_id, details, perm, target_base_oid)
}
#[instrument(skip(self), err(Debug))]
fn get_snapshot(&self, sha: gix::ObjectId) -> Result<Snapshot> {
let repo = self.repo.get()?;
let commit = repo.find_commit(sha)?;
let details = commit
.message_raw()?
.to_str()
.ok()
.and_then(|msg| SnapshotDetails::from_str(msg).ok())
.ok_or(anyhow!("Commit is not a snapshot"))?;
let snapshot = Snapshot {
commit_id: sha,
created_at: commit.time()?,
details: Some(details),
};
Ok(snapshot)
}
#[instrument(skip(self), err(Debug))]
fn snapshots_iter(
&self,
oplog_commit_id: Option<gix::ObjectId>,
exclude_kind: Vec<OperationKind>,
include_kind: Option<Vec<OperationKind>>,
) -> Result<impl Iterator<Item = Result<Snapshot>>> {
let repo = self.repo.get()?.clone();
let next_commit_id = match oplog_commit_id {View on GitHub (pinned to caf1f223d3)
Solutions
- Only pass shas that come from the oplog itself: oplog_head() or snapshots_iter() entries
- If integrating by sha from external input, validate it first by walking snapshots_iter and matching the id
- If snapshots from an old version fail to parse, update the app — parsers keep compatibility for known formats
- Inspect the commit directly (`git cat-file commit <sha>`) to confirm whether the message really lacks SnapshotDetails
Example fix
// before
let snap = ctx.get_snapshot(user_provided_sha)?; // user commit -> 'Commit is not a snapshot'
// after: only use shas produced by the oplog
let Some(head) = ctx.oplog_head()? else {
return Ok(());
};
let snap = ctx.get_snapshot(head)?; Defensive patterns
Strategy: validation
Validate before calling
// only accept shas the oplog itself produced
let valid: HashSet<gix::ObjectId> = ctx
.snapshots_iter(None, vec![], None)?
.filter_map(|s| s.ok().map(|s| s.commit_id))
.collect();
if !valid.contains(&requested_sha) {
anyhow::bail!("{requested_sha} is not an oplog snapshot");
}
let snap = ctx.get_snapshot(requested_sha)?; Type guard
fn is_oplog_snapshot(ctx: &Context, sha: gix::ObjectId) -> bool {
ctx.snapshots_iter(None, vec![], None)
.map(|mut it| it.any(|s| s.ok().is_some_and(|s| s.commit_id == sha)))
.unwrap_or(false)
} Try / catch
match ctx.get_snapshot(sha) {
Ok(snap) => Ok(Some(snap)),
Err(err) if err.to_string().contains("Commit is not a snapshot") => Ok(None), // caller passed a non-oplog commit
Err(err) => Err(err),
} Prevention
- Never feed user/branch commit shas into get_snapshot — source shas only from oplog_head()/snapshots_iter()
- Treat external sha input (URLs, saved settings) as untrusted and verify against the oplog before use
- Keep snapshot message parsing backward compatible when changing SnapshotDetails formats
When it happens
Trigger: Calling get_snapshot(sha) with an arbitrary workspace/user commit sha instead of one produced by the oplog; passing a sha obtained from user branches, gitbutler/workspace, or stash-like commits; an oplog commit written by a much older version whose message format no longer parses; a commit whose message was rewritten externally.
Common situations: Frontend or SDK code feeding a branch head sha where a snapshot sha is expected; deserialization drift after format changes between app versions; sha read from a stale settings/URL parameter; restoring by sha copied from an old timeline entry after a format change.
Related errors
- Could not get a tree of all applied virtual branches merged
- cannot snapshot a null target commit id
- targetRef in project_meta.toml is not a remote-tracking bran
- targetCommitId in project_meta.toml is null
- Invalid conflict stage '{}'
AI-assisted analysis of gitbutlerapp/gitbutler@caf1f223d3 (2026-08-20).
Data as JSON: /api/errors/6bc4d6d3c361cdc2.
Report an issue: GitHub.