The four object types
Everything git stores is one of four objects, each named by the SHA of its own contents. Change a byte, get a different object. Nothing is ever edited in place (Pro Git §10.2).
blob
The contents of one file. No filename, no permissions, no history — just bytes.
Two identical files anywhere in the repo, at any point in history, are the same blob stored once.
tree
A directory listing: mode · type · hash · name per entry. Entries point at blobs (files) or other trees (subdirectories).
A tree is the snapshot of one directory. The root tree is the snapshot of the project.
commit
One root tree, zero or more parent hashes, an author, a committer, and a message.
0 parents = root commit. 1 = ordinary. 2 or more = merge.
tag (annotated)
A named, message-carrying, optionally signed pointer at another object — usually a commit.
Lightweight tags are not objects at all; they are just refs.
Commit anatomy
tree 4b825dc642cb6eb9a060e54bf8d69288fbee4904the whole snapshot, not a diffparent 7d2e4033a1c9b0e5f1d2a8c73e0b6a49f5c81d3eabsent on root; twice on a mergeauthor Ada Lovelace <ada@ex.com> 1753900000 +0100who wrote it; survives rebasecommitter Ada Lovelace <ada@ex.com> 1753900000 +0100who applied it; rewritten by rebase blank line, mandatoryAdd rate limiting to the API clientmessage: everything after itgit cat-file -p HEAD prints them. There is no field for "the diff", because there is no diff.The five invariants
- A commit is a snapshot, not a diff. Every diff you have ever seen in git was computed on demand by comparing two trees. Storage deduplicates unchanged blobs and later packs objects as deltas — but that is compression, not identity.
- The hash is derived from the content, including the parent hash. So a commit's identity encodes its entire ancestry.
- Rewriting anything renumbers everything above it. Change a commit and every descendant gets a new hash, because each one contains the hash below it. That is rebase, amend, squash and cherry-pick in one sentence.
- Pointers only run backwards. A commit names its parent. Nothing names its children. Descendants are found by walking back from known tips.
- Nothing is deleted, only abandoned. A commit no ref can reach is unreachable, not gone. Default garbage-collection grace is 30 days for unreachable reflog entries, 90 for reachable ones (git-gc).
Refs, and HEAD
Objects are immutable. Refs are the only mutable thing in a repository, and they are files (Pro Git §10.3).
| File | Contents |
|---|---|
.git/refs/heads/main | One 40-character commit hash plus a newline — 41 bytes. That is the whole branch. |
.git/HEAD | Normally the text ref: refs/heads/main — a reference to a branch, not to a commit. Detached, it holds a raw hash instead. |
.git/refs/tags/v1.0 | A hash. Points at a commit for a lightweight tag, or at a tag object for an annotated one. |
.git/packed-refs | The same refs, collected into one file after git gc. Look here when refs/heads/ seems empty. |
git commit writes a new commit object and overwrites the current branch file with its hash. HEAD does not change — it names the branch, not the commit. git switch does the opposite: it changes HEAD to name a different branch and rewrites the working tree, and moves no branch file at all.
Inspection commands
| Plumbing — look at raw objects | Shows |
|---|---|
git cat-file -p <ref> | The raw contents of any object, pretty-printed. |
git cat-file -t <ref> | Its type: blob, tree, commit or tag. |
git cat-file -p 'HEAD^{tree}' | The root tree of HEAD — the snapshot's top-level directory listing. |
git rev-parse <ref> | Resolve any reference expression to a full 40-character hash. |
git hash-object -t commit --stdin | Compute — without writing — the hash of the content you pipe in. |
git ls-tree -r HEAD | Every file in the snapshot, recursively, with its blob hash. |
| Porcelain — what you'll actually type | Shows |
|---|---|
git log --graph --oneline --decorate --all | The shape of the whole graph, every branch, one line each. The auditing command. |
git show --stat <ref> | One commit: metadata plus which files changed and by how much. |
git show <ref>:<path> | The contents of one file as of one commit. |
git log --format='%h %an %ad %cn %cd' --date=short | Author against committer, side by side. Divergence means history was replayed. |
git log --oneline <a>..<b> | Commits reachable from b but not a. The basis of “what is on this branch”. |
git merge-base <a> <b> | The commit a merge of these two would use as its reference. Neither branch. |
Naming a commit
Anywhere git wants a commit, all of these work. They resolve to a hash before anything else happens (gitrevisions(7)).
| Expression | Means |
|---|---|
HEAD | The commit currently checked out. |
c81b95 | Any unambiguous hash prefix — usually 7 or more characters. |
HEAD~3 | Three steps back along first parents. “Three commits ago.” |
HEAD^ | The first parent. HEAD^2 is the second parent — only meaningful on a merge. |
main@{2.days.ago} | Where main pointed two days ago, per the reflog. |
HEAD^{tree} | The tree object of that commit, rather than the commit itself. |
origin/main..HEAD | Everything you have that the remote does not. What a push would send. |
~ walks backwards through generations, always taking the first parent. ^ picks which parent of a single commit you mean. On a linear history the two behave identically, which is why the difference stays hidden until you are standing on a merge commit and need the other side.
- Pro Git §10.2 — Git ObjectsObject types, content-addressing, and the structure of a commit.
- Pro Git §10.3 — Git ReferencesRefs,
HEAD, and whatpacked-refsis for. - gitrevisions(7)The complete grammar for naming commits. Denser than this page, and authoritative.
- inside .git — Julia EvansThe whole
.gitlayout drawn on one page. Worth printing next to this one.