GitoxideLabs/gitoxide · error
Cannot derive commit or tree from blob at
Error message
Cannot derive commit or tree from blob at {} What it means
The archive streaming code resolves the requested revision into a commit (for the timestamp) and a tree (for contents). If the resolved object is a blob, neither can be derived, so `fetch_rev_info` bails with the blob's id. Passing a blob id as an archive source is invalid by definition.
Solutions
- Pass a commit-ish (branch, tag, commit sha) instead of a blob id
- Resolve `HEAD:file.txt`-style specs to the containing commit before invoking archive
- Verify with `gix cat <id>` or `git cat-file -t <id>` that the object is a commit or tree
Example fix
// before gix::archive::stream(repo, "e1234deadbeef" /* blob id */, out, path_fmt)?; // after gix::archive::stream(repo, "HEAD", out, path_fmt)?;
Defensive patterns
Strategy: validation
Validate before calling
let obj = repo.rev_parse_single(rev)?.object()?;
if obj.kind == gix::object::Kind::Blob {
return Err(anyhow!("{} is a blob; pass a commit or tree", obj.id));
} Type guard
fn is_archivable(obj: &gix::Object<'_>) -> bool {
matches!(obj.kind, gix::object::Kind::Commit | gix::object::Kind::Tree | gix::object::Kind::Tag)
} Try / catch
match archive::stream(repo, rev, out, path) {
Err(e) if e.to_string().starts_with("Cannot derive commit or tree from blob") => {
eprintln!("resolve a commit-ish instead of a blob id");
}
other => other?,
} Prevention
- Always verify object kind (`git cat-file -t`) before passing an id to archive
- Avoid `path:file` revspecs when you want a whole-tree archive
- Validate user-supplied revs resolve to commits/tags before archiving
When it happens
Trigger: Calling `archive::stream` (or `fetch_rev_info`) with a revspec that resolves to a blob object, e.g. `gix archive <blob-sha>` or a spec like `HEAD:file.txt`.
Common situations: Copy-pasting a file/blob hash instead of a commit or tag hash; scripting revspec resolution that accidentally yields a blob path spec; typo'd ref names that fall back to object ids.
Understand the failure class
Background: "is not a compatible type" / "cannot merge" errors: when a value's type doesn't match what the library requires — this error's family across 65 libraries.
Related errors
- Tried to use as tree, but was
- Tried to use as commit, but was
- Tried to use as tag, but was
- the squash target must be a strict ancestor of the source
- the copy source and target must differ
AI-assisted analysis of GitoxideLabs/gitoxide@e73179060b (2026-09-08).
Data as JSON: /api/errors/f1b883eea59dc1c2.
Report an issue: GitHub.
Appendix: source
Thrown at gitoxide-core/src/repository/archive.rs:91
)?;
entries.show_throughput(start);
bytes.show_throughput(start);
Ok(())
}
fn fetch_rev_info(
object: gix::Object<'_>,
) -> anyhow::Result<(Option<gix::date::SecondsSinceUnixEpoch>, gix::ObjectId)> {
Ok(match object.kind {
gix::object::Kind::Commit => {
let commit = object.into_commit();
(Some(commit.committer()?.seconds()), commit.tree_id()?.detach())
}
gix::object::Kind::Tree => (None, object.id),
gix::object::Kind::Tag => fetch_rev_info(object.peel_to_kind(gix::object::Kind::Commit)?)?,
gix::object::Kind::Blob => bail!("Cannot derive commit or tree from blob at {}", object.id),
})
}
fn format_from_ext(path: &Path) -> anyhow::Result<archive::Format> {
Ok(match path.extension().and_then(std::ffi::OsStr::to_str) {
None => bail!("Cannot derive archive format from a file without extension"),
Some("tar") => archive::Format::Tar,
Some("gz") => archive::Format::TarGz {
compression_level: None,
},
Some("zip") => archive::Format::Zip {
compression_level: None,
},
Some("stream") => archive::Format::InternalTransientNonPersistable,
Some(ext) => bail!("Format for extension '{ext}' is unsupported"),
})
}
View on GitHub (pinned to e73179060b)