How it works
The explanation, with examples you can run.
Look inside a commit
Make a repository with one file and ask Git what it stored. The commands below were run with Git 2.50; your commit hash will differ because it includes your name and the time, but the blob hash will match exactly.
git init -q -b main demo && cd demoecho "hello" > greeting.txtgit hash-object greeting.txtgit add greeting.txtgit commit -q -m "Add greeting"git cat-file -p HEADce013625030ba8dba906f756967f9e9ca394464atree 57e9529754dc514a3ec10db2ff882018fbe1fcbfauthor Ada <ada@example.com> 1767607200 +0000committer Ada <ada@example.com> 1767607200 +0000 Add greetinggit cat-file -p prints any object. The commit is just text: a tree hash, author and committer lines with Unix timestamps, a blank line and the message. This first commit has no parent line. Follow the tree:
git cat-file -p 'HEAD^{tree}'git cat-file -p ce01362100644 blob ce013625030ba8dba906f756967f9e9ca394464a greeting.txthelloThe tree holds the name and the file mode (100644 is a normal file, 100755 executable, 040000 a subdirectory). The blob holds only the bytes.
What the hash is a hash of
A blob's name is the SHA-1 of a short header (blob, a space, the size in bytes, a NUL byte) followed by the content. You can reproduce it without Git (shasum on macOS, sha1sum on Linux):
printf 'blob 6\0hello\n' | shasumce013625030ba8dba906f756967f9e9ca394464a -This is content addressing: the name is derived from the data, so the same content always gets the same name and any corruption is detectable. Copy the file into a new folder and commit; Git writes a new tree for the folder and a new root tree and commit, but reuses the blob.
mkdir docs && cp greeting.txt docs/copy.txtgit add docs && git commit -q -m "Copy greeting into docs"git cat-file -p 'HEAD:docs'git count-objects100644 blob ce013625030ba8dba906f756967f9e9ca394464a copy.txt6 objects, 24 kilobytesSix objects: one blob, three trees (two root trees and docs) and two commits. The second commit's text has a parent line naming the first, and that chain of parents is the history.
Why this matters
- Snapshots, not diffs. Each commit names a complete tree. Unchanged files and folders are shared by hash, so snapshots are cheap.
git diffandgit log -pcompute changes on demand. - Immutable. An object never changes. Amending, rebasing and cherry-picking all make new commits; the old ones stay in the database until garbage collection removes unreachable ones.
- Integrity. A commit hash covers its tree, which covers every blob, and its parent, which covers all earlier history. If any byte changes, every hash above it changes.
- Packs. Loose objects are zlib-compressed files under
.git/objects/xx/.git gcand pushes and fetches bundle them into packfiles that store similar objects as deltas. That is a storage detail; the model is still snapshots.
New repositories still use SHA-1 by default (with collision detection built in). git init --object-format=sha256 creates a SHA-256 repository, but hosting support for it is still limited, so most teams stay on the default for now.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.