Four object types, and that is all
Every one is stored under the SHA-1 of its content. That single idea gives you deduplication (the same file in fifty commits is stored once), integrity (a changed byte is a changed id) and cheap branching (a branch is a forty-character file).
| Object | Holds | Points at |
|---|---|---|
| blob | the bytes of one file — no name, no path | nothing |
| tree | a directory listing: names, modes, ids | blobs and trees |
| commit | a message, an author, a time | one tree, and its parents |
| tag | an annotated tag | usually a commit |
The id is computed, not assigned
That is the whole algorithm for a blob. A tree is a sorted list of mode name\0 plus the raw twenty bytes of each id, hashed the same way; a commit is the text you saw in the last lesson, hashed the same way.
$ echo "hello" | git hash-object --stdin
ce013625030ba8dba906f756967f9e9ca394464a
# what was hashed: "blob 6\0hello\n"
# type, a space, the length, a NUL byte, then the content
Run git hash-object on a file here and you will get the same forty characters your own machine gives for the same content. The lab computes real SHA-1 over the real object format — that claim is tested against the git binary in the repository's own test suite.
Follow the chain yourself
Three commands and you are looking at your own bytes. Nothing in git is hidden; .git is a directory of files you can read.
$ git cat-file -p HEAD
tree 9a2f... ← the snapshot
parent 23fd...
author …
$ git cat-file -p 9a2f...
100644 blob ce01... notes.md ← the file, by id
$ git cat-file -p ce01...
hello
Why the same content always gets the same id
Copy a file and git stores nothing new. Revert a file to what it was three commits ago and the blob is already there. Rename a file and only the tree changes — which is why git has no "rename" operation and detects renames by comparing content afterwards.
echo "same" > a.txt
echo "same" > b.txt
git add .
# the tree has two entries — pointing at ONE blob
Where it all lives
Deleting .git deletes the history and leaves your files. Copying .git copies the history. A branch really is a file with an id in it — cat .git/refs/heads/main prints it.
.git/
objects/ loose objects, then packed into .pack files
refs/heads/main a file containing forty characters
refs/tags/v1.0
HEAD "ref: refs/heads/main"
index the staging area, in a binary format
config this repository's settings
Git uses SHA-1 with a hardened check against the known collision attack, and the object format includes a length prefix, which makes a malicious collision much harder. The migration to SHA-256 exists and is slow because every id in every system would change. For your purposes: ids are unique.