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

  1. Pass a commit-ish (branch, tag, commit sha) instead of a blob id
  2. Resolve `HEAD:file.txt`-style specs to the containing commit before invoking archive
  3. 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

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


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)