This document summarizes the object formats supported by git-internal, how IDs are hashed, and how they map to canonical Git formats, based on the implementations in internal/object.
- Storage format:
<type> <size>\0<raw-bytes>, wheretypeisblob/tree/commit/tagandsizeis the raw data length (decimal string). - Hashing:
ObjectHash::from_type_and_data_for_kind(HashKind, ObjectType, data)produces an ID for an explicit repository hash kind (SHA-1, SHA-256, or BLAKE3-256) and fails closed (HashError) for delta types;ObjectHash::from_type_and_data(ObjectType, data)is the thread-local wrapper (switch viaset_hash_kind/set_hash_kind_for_test). - Hash kinds and compatibility:
HashKind::Sha1(20 bytes / 40 hex) andHashKind::Sha256(32 bytes / 64 hex) are the standard Git object formats.HashKind::Blake3(32 bytes / 64 hex,as_str() == "blake3",is_git_standard() == false) is a git-internal / Libra extension: a BLAKE3 repository is a separate object namespace, existing SHA-1/SHA-256 repositories are never converted in place, and unmodified standard Git cannot read or exchange BLAKE3 objects. Because SHA-256 and BLAKE3 IDs share the same width, a raw 64-hex / 32-byte value is only meaningful inside a known repository kind: usefrom_hex_for_kind/from_bytes_for_kind/from_stream_for_kind, or the tagged formsha1:HEX/sha256:HEX/blake3:HEX(to_tagged_string/from_tagged_str) when an ID leaves its repository (APIs, indexes, logs). The legacyObjectHash::from_str(40 → SHA-1, 64 → SHA-256) andfrom_bytes_infer_kind(20 → SHA-1, 32 → SHA-256) never produce BLAKE3.ObjectHash::Sha256andObjectHash::Blake3with identical bytes are distinct values (ensure_kindrejects the other kind). BLAKE3 only changes the repository object ID: the AIIntegrityHashstays SHA-256 under every repository kind, and application-level digests are untouched. - Repository hash context: the thread-local kind is only a compatibility default for the single-repository workflow. Code that may run on a worker thread, async task, cache or protocol callback belonging to another repository must use the explicit-kind API:
ObjectTrait::object_hash_for_kind(kind),ObjectTrait::from_buf_read_with_kind(reader, size, kind)(withReadBoxed::new_with_kind), and the*_with_kindconstructors listed per object below. The*_with_kindconstructors,ReadBoxed::new_with_kindandobject_hash_for_kindreturnResult<_, GitError>and never panic, and they reject references whoseObjectHashbelongs to another kind (GitError::InvalidHashValuewith ahash kind mismatchmessage).from_buf_read_with_kindfinalizes the ID without panicking and then delegates to the type'sfrom_bytes:Tree::from_bytesslices entry IDs with the width of the hash it is given (hash.kind()) and fails closed on malformed entries, so trees load correctly for any repository kind on any thread;Commit::from_bytesandTag::from_bytesparse theirtree/parent/objectreferences as IDs ofhash.kind()(a 64-hex reference is never guessed to be SHA-256 by length) and return errors — never panic — on wrong-width, non-hex, non-UTF-8 or truncated references and on malformedauthor/committer/taggersignature lines (Signature::from_datais fail-closed).Commit::from_bytesadditionally rejects any header line other thanparent/author/committerin its header block, whileTag::from_byteskeeps its legacy tolerance of ignoring unknown header lines (it only errors on missing required fields); the commit message bytes after thecommitterline are still taken as-is (legacy behaviour).Note::from_bytesfills its placeholder target withObjectHash::zero_for_kind(hash.kind()). - Types:
ObjectTypeoffersto_string/to_u8/from_u8/from_string, covering base objects (Commit/Tree/Blob/Tag) and delta objects (OffsetDelta/HashDelta/OffsetZstdDelta—extension). - Serialization: Each object’s
to_datareturns<type><size>\0<body>;ObjectHash::to_string()emits hex,to_data()returns raw bytes.
- Location:
object/blob.rs. - Meaning: File content snapshot, no path/permission (those live in Tree).
- Structure:
Blob { id: ObjectHash, data: Vec<u8> }. - Build:
Blob::from_content/from_content_bytesauto-compute the hash with the thread-local kind;from_content_with_kind(kind, ..)/from_content_bytes_with_kind(kind, ..)take the repository kind explicitly;from_bytes(data, hash)parses with a known hash. - Serialize:
to_data()returns raw content (header is implied when hashing withObjectHash::from_type_and_data).
- Location:
object/tree.rs. - TreeItem format:
"<mode> <name>\0<id-bytes>"; modes include100644/100755/120000/160000/40000(gitlink). - Structure:
TreeItem { mode: TreeItemMode, id: ObjectHash, name: String };Tree { id, tree_items: Vec<TreeItem> }. - Build:
Tree::from_tree_items(items)computes the tree hash with the thread-local kind;from_tree_items_with_kind(kind, items)/rehash_with_kind(kind)take the repository kind explicitly and require every entry ID to belong to that kind;rehashrecomputes after modifications with the thread-local kind. - Parse:
Tree::from_bytes(data, hash)splits IDs using the width ofhash.kind()(20/32 bytes) viaTreeItem::from_bytes_with_kind(bytes, kind), which fails closed on malformed entries; the legacyTreeItem::from_bytes(bytes)uses the thread-local kind. TreeItem parsing has a GBK fallback for non-UTF-8 names.
- Location:
object/commit.rs. - Field order:
tree <tree-id>, zero or moreparent <parent-id>,author <signature>,committer <signature>, blank line, then message (may include signatures). - Structure:
Commit { id, tree_id, parent_commit_ids, author, committer, message }. - Build:
Commit::new(explicit signatures) orfrom_tree_id(convenience with current-time signatures); both useObjectHash::from_type_and_data(thread-local kind) to derive the ID.Commit::new_with_kind(kind, ..)/from_tree_id_with_kind(kind, ..)derive the ID for an explicit kind and requiretree_idand every parent ID to belong to it. - Parse:
from_bytes(data, hash)splits lines, parsestree/parentreferences withObjectHash::from_hex_for_kind(hash.kind(), ..)(wrong width / non-hex →GitError::InvalidHashValuewith theHashErrordiagnostic; structural errors →GitError::InvalidCommitObject), and usesSignature::from_datafor author/committer. - Helper:
format_messageskips PGP signature blocks or returns the first non-empty line.
- Location:
object/tag.rs. - Format:
object <object-hash>
type <object-type>
tag <tag-name>
tagger <name> <email> <timestamp> <tz>
<message>(after a blank line) - Structure:
Tag { id, object_hash, object_type, tag_name, tagger, message }. - Build:
Tag::new(object_hash, object_type, tag_name, tagger, message); hash is computed from serialized content with the thread-local kind.Tag::new_with_kind(kind, ..)derives the ID for an explicit kind and requiresobject_hashto belong to it. - Parse:
from_bytes(data, hash)validates UTF-8, parses theobjectreference withObjectHash::from_hex_for_kind(hash.kind(), ..)(wrong width / non-UTF-8 →GitError::InvalidTagObjectcarrying kind and expected/actual lengths), errors on missing required fields, and ignores unknown header lines (legacy tolerance);to_dataemits the format above.
- Location:
object/note.rs. - Meaning: An annotation attached to an object; internally treated as a Blob (
get_typereturns Blob), and hashed using Blob rules. - Build/Parse:
Note::from_content(content)builds a note for a placeholder target (legacy: SHA-1 zero ID);Note::new(target_object_id, content)associates it to a specific object.Note::new_with_kind(kind, target, content)/from_content_with_kind(kind, content)derive the ID for an explicit kind, require the target to belong to it, and useObjectHash::zero_for_kind(kind)as the placeholder. Usefrom_bytes(data, hash)to parse existing data; its placeholder target isObjectHash::zero_for_kind(hash.kind()).
- Location:
object/signature.rs. - Layout:
<role> <name> <email> <timestamp> <tz>, whereroleisauthor/committer/tagger(SignatureType). - Functions:
Signature::from_dataparses a byte sequence and fails closed (GitError::InvalidSignatureType) on a missing role/</>/space separator, non-UTF-8 text, a non-numeric timestamp or a timezone that is not canonical[+-]HHMM(an empty name is allowed);to_dataserializes;newcreates a signature with a given role/name/email from the current local time and formats the timezone withformat_timezone(canonical[+-]HHMM, e.g.-0230).
- Location:
object/utils.rs. - Contents: Currently minimal; most shared I/O/hash helpers live in top-level
utils.rs.
- Loading from a zlib stream:
ReadBoxed::new_with_kind(reader, obj_type, size, kind)seeds the hasher for the repository kind (ReadBoxed::newuses the thread-local kind);ObjectTrait::from_buf_read_with_kindthen finalizes the ID from that hasher and fails closed if the requested kind differs from the reader's. - Pack/idx hash context:
Pack::new_with_hash_kind,PackEncoder::new_with_hash_kind/new_with_idx_and_hash_kind,encode_and_output_to_files_with_hash_kind,PackStats::analyze_with_hash_kind,Pack::decode_pack_object_with_kindandIndex::{from_file,to_file,load,save,refresh}_with_hash_kindtake the repositoryHashKindexplicitly; the parameterless forms are thread-local compatibility wrappers (encode_and_output_to_filescaptures the thread-local kind on the calling thread before the future is polled).IdxBuilderderives its kind from the pack hash. Object IDs, ref-delta base IDs, the pack trailer, idx object names and both idx checksums all use that kind; the pack cache spills objects to<layout>/<2 hex>/<kind>-<hex>files so same-width SHA-256 and BLAKE3 IDs never alias. - BLAKE3 packs (git-internal/Libra extension, not readable by standard Git): 32-byte object IDs and ref-delta bases, a BLAKE3 pack trailer and a BLAKE3 idx checksum, always in idx v2 (the only version this crate reads or writes;
validate_idx_v2_headerfails closed on any other version for every kind). A BLAKE3 pack is byte-compatible in layout with a SHA-256 pack, so the kind must come from the caller — a SHA-256 decoder rejects a BLAKE3 trailer (hash mismatch) rather than misreading it.ObjectHash::from_bytes_infer_kindis deprecated and unused by the pack/index/protocol paths. - Pack decode yields
Entrywithobj_type,hash,data; these can be parsed by the object modules above. - Pack encode expects
ObjectHashplus raw data;PackEncoderusesObjectTypeto craft headers and validate hashes. - Protocol (upload-pack/receive-pack) cares about object ID/type consistency; content parsing is left to the caller or higher layers as needed.