REPORT
CEPATO Technical Note2026.03.31

What Even Is Git? Git's First Principles and the History of Its Birth, Explained in One Article

Why did Linus design Git? Why is rebase dangerous in multi-person collaboration? Git's underlying design from first principles.

Chongxi
Author:Chongxi
steam: —·spotify: —
Tags:#Git#Linus
ANALYSIS

0. Introduction#

In 2005, the Linux kernel was the world’s largest open-source collaboration project

The 1000+ contributors worldwide were using a commercial version control tool called BitKeeper

In April 2005, on a weekend, an Australian programmer named Andrew Tridgell (the guy who wrote Samba) reverse-engineered BitKeeper

Larry McVoy, the man behind BitKeeper, found out and announced it would stop providing free licences to the entire Linux community

Linus lost his tool overnight

Linus didn’t panic. He first spent two weeks studying every version control tool on the market — CVS, SVN, Monotone, Darcs…

Then he reached a conclusion:

These things are all garbage.

The exact words in his email were:

No existing system meets my needs, so I decided to write my own

0.1 Why?#

Linus’s needs weren’t those of an ordinary developer; what he managed was the most complex collaborative project in the world

His daily work looked like this:

Developers around the world send their patches to the maintainers of each subsystem, the subsystem maintainers organise them and send them to Linus, and Linus finally decides what goes into the kernel

This is a tree-shaped network of trust, not a centralised commit system

So the tool he needed had to satisfy:

1. It has to work offline Developers must be able to commit, view history and branch without a network connection

2. It has to be fast The Linux kernel is millions of lines of code, and operations must complete in seconds or even milliseconds. He mocked CVS at the time: “If an operation takes more than 3 seconds, the design is wrong.”

3. History must be tamper-proof He had to be able to verify that the code a developer sent him, from the moment they wrote the first line to the moment it reached Linus’s hands, hadn’t been touched by anyone along the way. Not by trust, but by mathematics.

4. Branch merging has to be good enough The Linux development model is inherently parallel, with dozens of development lines advancing at once; merging must be reliable and must be fast. SVN’s merges are a nightmare — unacceptable to him

April 3, 2005: Linus starts writing Git

April 7, 2005: Git can host its own source code

April 18, 2005: Linux kernel developers start collaborating with Git

June 2005: Linux 2.6.12 is officially released with Git

From zero to hosting the world’s largest open-source project in under three months

0.2 diff vs snapshot#

The essence of version control isn’t recording “how the files changed”, but recording “what the complete state of the project was at each moment”

CVS and SVN record diffs, i.e. which lines changed this time

Linus thought that was wrong

What he wanted to record was a snapshot; every commit is a complete snapshot of the entire project

This one decision led to almost all of Git’s design

1. One decision led to all the design#

Linus sat there not thinking “how should I design a version control tool”

He was thinking about a more fundamental question:

What does “the same code” mean?

1000 developers, each with a copy of the Linux kernel code on their machine. How do I know whether yours and mine are in the same state? How do I know which state the patch you sent me was written against?

SVN’s answer at the time was to look at the revision number: r1042 is r1042

Linus thought that was bullshit. Revision numbers are assigned by a central server; you have to trust that server. And a revision number is just a counter; it contains no information about the content itself

1.1 The content itself is the identity#

Linus’s idea was:

Don’t identify a piece of code with an externally assigned ID; use the hash of the code content itself

Specifically, hash the entire content of a file with SHA-1 to get a 40-character string

PRTCL // PLAINTEXT
SHA-1("Chongxi") = f5c3bc7e6fcd7b7b8d04b99f5d7e607302d3a911

This string has a mathematical property:

If the content changes, the hash necessarily changes. If the hash hasn’t changed, the content necessarily hasn’t changed

This isn’t a protocol, isn’t trust — it’s a mathematical proof

So two people holding the same hash value can confirm the files in their hands are completely identical, without needing any third party

1.2 But a single file isn’t enough; he needs the whole project#

A file can be hashed; what about the whole project?

The Linux kernel has tens of thousands of files spread across hundreds of directories. Linus needed a way to represent the state of the entire project with one hash value

His solution was to hash recursively:

PRTCL // PLAINTEXT
the content of main.c → hash A
the content of util.c → hash B
the src/ directory → hash(A + B + filename) = hash C
the content of README → hash D
the root directory → hash(C + D + filename) = hash E

The root directory’s hash E is computed from the hashes of all child nodes

This means:

If any file changes by a single byte, every parent node’s hash above it changes, all the way up, and the root hash changes too

Conversely: if the root hash hasn’t changed, not a single byte of the entire project has changed

This is Git’s tree object, and also the core idea of the Merkle Tree

1.3 Snapshot#

With tree objects, Linus could lock the state of the entire project to one hash value at any moment

Then he wrapped another layer around the outside, adding metadata:

PRTCL // PLAINTEXT
commit {
tree: e5f8c2... ← the root hash of the entire project at this moment
parent: a3d9f1... ← the hash of the previous commit
author: Linus
time: 2005-04-07
message: "Initial commit"
}

Then he hashed this entire structure once more to get this commit’s hash value

There’s a very crucial point here:

The commit’s hash contains the parent’s hash

This means you can’t quietly modify the content of some commit in history without changing the hash. Change that commit and its hash changes; the parent hash recorded by the next commit no longer matches, and the one after that doesn’t match either — the entire chain breaks

History is locked with cryptography — not by permission control, not by trust, but by mathematics

1.4 snapshot vs diff: who will win#

SVN stores:

PRTCL // PLAINTEXT
r1: the original file
r2: line 3 changed to this
r3: line 17 deleted, line 20 added this

To know the state at r3, you have to stack up all the diffs from the beginning and compute

Git stores:

PRTCL // PLAINTEXT
commit1: hash E1 of the entire project
commit2: hash E2 of the entire project
commit3: hash E3 of the entire project

To know the state at any moment, just take that hash, find the corresponding tree, and expand it. No computation, no replay

The cost is storage space? Git solved that with object deduplication: if a file hasn’t changed between two commits, their blob hashes are the same, and Git stores only one copy

1.5 What can we derive from this#

At this point, Linus had not yet invented branches, had not invented merge, had not invented any advanced concept

He had done only one thing:

Use hashes to lock the project state at each moment into a tamper-proof object, and use parent pointers to string these objects into a chain

Everything that remains — branches, merging, rebase — grew naturally out of this foundation

2. The object model#

Git isn’t a complex version control system

Linus’s earliest definition was very plain:

Git is a content-addressable filesystem. You put things in, it gives you a hash. Hold the hash, and you can get the thing back out at any time

That’s all

You can open any Git repository and look at the .git/objects/ directory; what’s stored inside is all the objects, split into folders by the first two characters of the hash:

PRTCL // PLAINTEXT
.git/objects/
a3/f8c2d9b1e4... ← this is one object file
d9/f3a1b2c4e5...
e5/c2f8a3d9b1...

The content of each file is compressed object data. A whole Git repository is, in essence, this folder

2.1 blob#

The simplest object in Git is called a blob; it stores the raw content of a file, without the filename, without any metadata

PRTCL // PLAINTEXT
blob "CEPATO"
↓ SHA-1
d1fcd7b25f99b9f9d1a577557f45c10b6c1f9642

A blob doesn’t know what it’s called, nor which directory it’s in

Filenames are a matter for the upper layer; blobs only care about content

This design has one direct benefit:

Two files with completely identical content at different paths — Git stores only one blob

2.2 tree#

Filenames and directory structure are handed to tree objects to manage

A tree object is just a table:

PRTCL // PLAINTEXT
tree d9f3a1... {
100644 blob 8c7e5a... main.c
100644 blob f7d8e9... util.c
040000 tree b2c4e5... src/
}

Each line records: file permissions, object type, hash value, name

Trees can nest trees, and that builds the complete directory structure

Then hash this table to get the hash value of this tree

Because a tree’s hash is computed from its children’s hashes:

PRTCL // PLAINTEXT
main.c's content changed
→ main.c's blob hash changed
→ the tree containing it changed
→ the upper tree hash changed
→ the root tree hash changed

Any change in any leaf is reflected all the way up to the root node

Let me clarify something here. A friend came to me with a question separately; I thought the question was good, so I’m putting it out separately to talk about

Git’s tree is not a hash table; it’s more like an ordered list, storing a mapping of filename → child node hash, but the lookup method is linear traversal, not hash-table O(1) addressing

Compared with the Patricia Trie part of an MPT

PRTCL // PLAINTEXT
Git tree: filename → child node hash // simple mapping, no lookup optimisation
MPT: path → leaf value // prefix-compressed, O(log n) lookup

The two look very similar on the surface — both are some kind of key pointing at a hash — but the design goals differ:

  • Git’s tree doesn’t need efficient lookup; you checkout a commit and Git just expands the whole tree, with no need to frequently query individual nodes
  • Ethereum’s MPT needs efficient lookup, because every transaction has to query the state of a particular account individually, and O(n) is unacceptable

So a simple structure is enough for Git, and only Ethereum needs a Patricia Trie for optimisation

In other words, Git’s tree is for describing state, MPT is for querying state efficiently

2.3 commit#

With trees, we can represent the state of the project at a certain moment

But two things are still missing: who created this state and when, and what its previous state was

That’s the commit object:

PRTCL // PLAINTEXT
commit a3f8c2... {
tree: d9f3a1... ← root tree, representing all files at this moment
parent: prev_hash ← the previous commit
author: Linus Torvalds
time: 2005-04-07 15:16:22
message: "Initial commit"
}

Then hash this entire structure to get this commit’s hash

Because the commit hash contains the parent hash, and the parent hash contains its parent, all the way back to the first commit

The entire history chain is locked by a single hash value

Holding the latest commit hash, you can verify that every change to every line of code, from the first day to today, hasn’t been touched by anyone

2.4#

Now let’s look at the three layers together:

PRTCL // PLAINTEXT
commit a3f8c2
└── tree d9f3a1 (root directory)
├── blob 8c7e5a main.c
├── blob f7d8e9 util.c
└── tree b2c4e5 src/
└── blob 3a9f1c helper.c

Each node is an independent object, stored in .git/objects/ and addressed by hash

There’s no containment relationship between them, only reference relationships. Each object records only its child nodes’ hashes, without copying their content


Suppose you make a second commit, changing only main.c:

PRTCL // PLAINTEXT
commit e5c2f8
└── tree 7a1b3d (the new root tree)
├── blob 9f2e4a main.c ← new blob
├── blob f7d8e9 util.c ← same as last time
└── tree b2c4e5 src/ ← same as last time

util.c and the src/ directory didn’t change, so Git reuses their objects directly — no copying, no re-storing

For two commits, Git created only three new objects: the new blob, the new root tree, and the new commit

Git stores snapshots, but because of object reuse, the space actually used is close to storing diffs

You can have your cake and eat it too

2.5 Branches#

Having derived this far, Git’s storage layer is already complete: a hash object database containing blobs, trees and commits, connected into trees by references, and strung into a timeline by parent pointers

But there’s one problem:

A commit’s hash is a string like a3f8c2d9b1e4f7..., and humans can’t memorise that

How do you find the latest commit?

3. Pointers#

The object database is built, holding thousands of objects, each with a 40-character hash address

Linus faced a very practical problem:

How do I know where the latest commit is?

Write the latest commit hash down on paper every time? That would be direct, sure

git stores this hash in a text file, and gives that file a human-readable name

That’s a branch. Yep, that simple

3.1 refs#

Git puts these text files together in the .git/refs/ directory:

PRTCL // PLAINTEXT
.git/refs/
heads/
main ← content: a3f8c2d9...
feature ← content: d9f3a1b2...
remotes/
origin/
main ← content: e5c2f8a3...
tags/
v1.0 ← content: f7d8e9b1...

You can open these files directly and look — inside is just one line of hash value, nothing else

PRTCL // BASH
Terminal window
cat .git/refs/heads/main
# a3f8c2d9b1e4f7a8b2c3d4e5f6a7b8c9d0e1f2a3

3.2 The essence of a branch#

So “creating a new branch” — what actually happens at the filesystem level?

PRTCL // BASH
Terminal window
git branch feature

What happened:

Create a new file called feature under .git/refs/heads/, and write the current commit’s hash into it

That’s it. No copying code, no copying history, none of the enterprise-grade operations you might imagine — just this simple

This is why creating a branch in Git is instant, while creating a branch in SVN requires copying the entire directory

3.3 How branches move#

You commit new code on the feature branch, producing a new commit b2c4e5...

What happened:

Update the hash in the .git/refs/heads/feature file to b2c4e5...

It’s just changing one line of text in one file

PRTCL // PLAINTEXT
Before the commit: feature file content = a3f8c2...
After the commit: feature file content = b2c4e5...

The branch follows you along; in essence it’s this file updating itself

3.4 HEAD#

Now you have two branches, main and feature. How does Git know which branch you’re currently on?

Add one more file, called HEAD, placed in .git/HEAD:

PRTCL // BASH
refs/heads/feature
cat .git/HEAD

It doesn’t store a commit hash, but a reference pointing to a branch

So:

PRTCL // PLAINTEXT
HEAD → refs/heads/feature → b2c4e5...(commit)

When you commit new code:

  1. Generate a new commit object
  2. The feature file is updated to the new hash
  3. HEAD doesn’t move; it still points to feature

3.5 What about switching branches#

PRTCL // BASH
Terminal window
git checkout main

What happened:

  1. Change .git/HEAD to:
PRTCL // PLAINTEXT
ref: refs/heads/main
  1. Based on the commit that main points to, expand the corresponding file tree into your working directory

You see the files change; in essence Git has rewritten your working directory with content from the object database

3.6 detached HEAD#

Now we understand that HEAD is a pointer to a branch

But if you checkout a commit hash directly:

PRTCL // BASH
Terminal window
git checkout a3f8c2

At this point there’s no branch it can point to, so HEAD can only store the hash directly:

PRTCL // BASH
Terminal window
cat .git/HEAD
# a3f8c2d9b1e4f7...

This is a detached HEAD — the head pointer detached

Why does everyone say the detached state is dangerous?

Because commits you make in this state produce new commits. But no branch file records the new commit’s hash

Once you switch to another branch, HEAD updates, and nothing points to that new commit’s hash anymore

It’s still lying in .git/objects/, but you can never find it again

Git periodically runs garbage collection, cleaning up objects that no reference points to. Your commit just disappears like that

3.7 Remote pointers#

Finally there’s another kind of pointer, placed under .git/refs/remotes/:

PRTCL // PLAINTEXT
origin/main ← content: the hash of the remote main at the last fetch

Its job is: record the state of the remote repository at your last sync

It’s read-only; you can’t commit on it directly. It only updates when you git fetch

What git push and git pull essentially do is make these local and remote pointers line up

3.8 Summary#

PRTCL // PLAINTEXT
HEAD
└── refs/heads/main → commit C4
└── refs/heads/feature → commit C5
refs/remotes/origin/main → commit C3 (the state at the last fetch)
refs/tags/v1.0 → commit C1 (doesn't move)

The object database is immutable; the pointer system is mutable
Every Git operation is essentially moving these pointers, or adding objects to the object database

4. The essence of the core everyday operations#

Let’s first set up a scenario; all the operations below are based on it:

PRTCL // PLAINTEXT
C1 ← C2 ← C3 main
↑
└── C4 ← C5 feature

main points to C3, feature points to C5, and C4 and C5 forked off from C3.

4.1 merge: create a new node#

You’re on main and run:

PRTCL // BASH
Terminal window
git merge feature

git does the following

  1. Find the branches’ parent node

main is at C3, feature is at C5; follow the parents upward and the parent node is C3

  1. diff
PRTCL // PLAINTEXT
C3's file state
What main changed between C3 → C3
What feature changed between C3 → C5

If different places changed, merge automatically. If the same place changed, conflict — you resolve it by hand

  1. Generate a new commit C6
PRTCL // PLAINTEXT
commit C6 {
tree: the merged file state
parent: C3 ← the main side
parent: C5 ← the feature side
}

C6 has two parents, which is entirely legal in Git’s object model

Then the main pointer moves to C6:

PRTCL // PLAINTEXT
C1 ← C2 ← C3 ← C6 main
↑ ↑
└── C4 ← C5 feature

The essence of merge is creating a new node in the object database, joining the two lines together, and moving the current branch pointer

History stays real; the fork and the join are both preserved

4.2 rebase: rebuild the nodes, change their dad#

PRTCL // PLAINTEXT
C1 ← C2 ← C3 main
↑
└── C4 ← C5 feature

Same scenario; you’re on feature and run:

PRTCL // BASH
Terminal window
git rebase main

git then replays the two commits C4 and C5 after C3

  1. Find the common parent node C3
  2. Extract the change content of C4 and C5
  3. Starting from main’s latest position C3, reapply these changes one by one, generating new commits
PRTCL // PLAINTEXT
C4 → C4'(parent changed from C2 to C3)
C5 → C5'(parent changed from C4 to C4')

C4’ and C4 may have the same file content, but completely different hash values, because the parent changed

Result:

PRTCL // PLAINTEXT
C1 ← C2 ← C3 ← C4' ← C5' feature
↑
main

History becomes a straight line, as if feature had always been developed sequentially after C3

The essence of rebase is copying a stretch of commit history to another position and rebuilding it; the original objects are still in the database, but no pointers point at them anymore


Note that C4 and C4’ both exist in .git/objects/

After the rebase, the feature pointer has moved to C5’, and no pointer points at the original C4 and C5 anymore

They get cleaned up when Git garbage collects

That’s why after a rebase we say “history has been rewritten”: the original commits are abandoned, and a batch of parallel objects has been forked

4.3 reset: move the pointer, create no new nodes#

PRTCL // BASH
Terminal window
git reset C3

This operation is the simplest and also the easiest to misunderstand

It does only one thing:
forcibly move the current branch’s pointer to the specified commit

PRTCL // PLAINTEXT
Before: main → C5
After: main → C3

C4 and C5 are still in the object database, but main no longer points at them

reset has three modes; the difference lies in how the working directory and staging area are handled:

PRTCL // PLAINTEXT
--soft move the pointer only; working directory and staging area untouched
--mixed move the pointer, clear the staging area, working directory untouched (default)
--hard move the pointer, clear the staging area, roll back the working directory too

From the pointer’s perspective, all three modes do the same thing. The only difference is how the files in your working directory change

4.4 cherry-pick: copy a single node#

PRTCL // BASH
Terminal window
git cherry-pick C4

You’re on main and want to bring over a single commit from feature

The operation:

Extract C4’s change content relative to its parent, reapply it at the current position, and generate a new commit C4’

PRTCL // PLAINTEXT
C1 ← C2 ← C3 ← C4' main
↑
└── C4 ← C5 feature

C4’ and C4 have the same content, different hashes

Just like rebase, both are “copying a commit to a new position and rebuilding it”, except rebase copies a stretch and cherry-pick copies one

A quick table:

OperationNew object?Moves pointer?Rewrites history?
mergeYes, creates a merge commitYesNo
rebaseYes, rebuilds commitsYesYes
resetNoYesDepends on the mode
cherry-pickYes, copies commitsYesNo

Every operation is a combination of two things: adding things to the object database, and moving pointers

4.5 Summary#

We can now understand any Git operation:

  • The object database only takes in, never gives out (except garbage collection); it’s append-only
  • Pointers can be moved freely
  • The history you see depends on where the pointers point

This is why Git almost never loses data — as long as the object is still in the database, you can always find where a pointer used to point via git reflog and fish the thing back out

Next we’ll talk about collaboration, and why you shouldn’t use rebase when there are multiple people

5. Collaboration#

First, set up a realistic scenario

You and Chongxi have each cloned the same remote repository. At the moment of cloning, all three of your states are completely identical:

PRTCL // PLAINTEXT
Remote origin: C1 ← C2 ← C3 main
Your local: C1 ← C2 ← C3 main
Chongxi's local: C1 ← C2 ← C3 main

Then we start working separately

5.1 What happens on clone#

Picture unrelated

clone isn’t simply “downloading the code”

More precisely:

Copy the entire remote object database to your machine, then store a read-only copy of all the remote pointers locally

PRTCL // PLAINTEXT
.git/refs/remotes/origin/main = the hash of C3

This origin/main is your local snapshot of the remote state; it records where the remote was the last time I synced with it

5.2 fetch#

You work for a while, and the remote has new commits

PRTCL // BASH
Terminal window
git fetch

Git does the following:

Download all new objects from the remote, and update the origin/main pointer. Your local main doesn’t move

PRTCL // PLAINTEXT
Remote: C1 ← C2 ← C3 ← C4 ← C5 main
Your local main: C1 ← C2 ← C3 main(unmoved)
origin/main: C1 ← C2 ← C3 ← C4 ← C5 (updated)

fetch does only one thing: sync objects, update remote pointers. Your work is completely unaffected

5.2 push: push local pointers to the remote#

You committed C4’ locally and want to push it to the remote:

PRTCL // BASH
Terminal window
git push

Git does the following

  1. Transfer the new local object (C4’) to the remote object database
  2. Ask the remote to move the main pointer to C4’

But the remote checks one thing:

Is the parent of your C4’ the commit the remote’s current main points to?

If it is, that’s called a fast-forward, and the remote moves the pointer forward directly, no problem at all

If it isn’t, the remote refuses and returns an error:

PRTCL // PLAINTEXT
! [rejected] main -> main (non-fast-forward)

Why it gets rejected

You and Chongxi both started working from C3:

PRTCL // PLAINTEXT
You: C1 ← C2 ← C3 ← C4' local main
Chongxi: C1 ← C2 ← C3 ← C4 pushed to the remote

Chongxi pushed before you, and the remote main is now at C4

You want to push C4’, but C4”s parent is C3, not C4

The remote: your history and my history have diverged; I don’t know how to merge them, you figure it out yourself

5.3 pull: fetch + merge#

PRTCL // BASH
Terminal window
git pull

It’s essentially two steps:

PRTCL // PLAINTEXT
git fetch ← sync remote objects first
git merge ← then merge origin/main into local main

After merging, a merge commit C5 is generated; then you push again, and this time C5’s parent includes C4, so the remote accepts it

PRTCL // PLAINTEXT
C1 ← C2 ← C3 ← C4 ← C5 remote main(C5 is a merge commit)
↑ ↑
Chongxi You

5.4 How conflicts happen#

You and Chongxi changed the same place in the same file

Git does a three-way comparison:

PRTCL // PLAINTEXT
Common parent C3: line 5 is "meow"
Chongxi's C4: line 5 changed to "qwq"
Your C4': line 5 changed to "uwu"

Git doesn’t know which one to use, and can only stop and let you decide:

PRTCL // PLAINTEXT
<<<<<<< HEAD
uwu
=======
qwq
>>>>>>> origin/main

This isn’t a defect in Git; it’s a real conflict. A machine can’t decide business logic on your behalf

5.5 Why rebase is dangerous in a multi-person scenario#

Now let’s go back to the question mentioned earlier and explain it with the object model

You and Chongxi both created a feature branch from C3, and Chongxi has already pushed his C4 and C5 to the remote:

PRTCL // PLAINTEXT
Remote feature: C1 ← C2 ← C3 ← C4 ← C5
Your feature: C1 ← C2 ← C3 ← C4 ← C5

You run a rebase on feature locally, generating C4’ and C5’:

PRTCL // PLAINTEXT
Your feature: C1 ← C2 ← C3 ← C4' ← C5'

Then you force-push to the remote:

PRTCL // BASH
Terminal window
git push --force // blindly push --force and you'll get beaten to death

The remote feature becomes:

PRTCL // PLAINTEXT
Remote feature: C1 ← C2 ← C3 ← C4' ← C5'

The original C4 and C5 have disappeared from the remote

But Chongxi still has them locally:

PRTCL // PLAINTEXT
Chongxi's feature: C1 ← C2 ← C3 ← C4 ← C5

When Chongxi fetches again, he finds the remote history and his local history completely don’t match, because C4 and C4’ have different hashes, and Git considers them two completely unrelated commits

My local repository plunges into chaos

Root cause: rebase rewrote the commit hashes, and a hash is the only ID card in the Git world. You changed an ID card somebody else depends on, and their world collapses

  • So there’s this iron rule in collaboration

Never rebase commits that have already been pushed to the remote and that others may be building on

For your own commits that haven’t been pushed out, rebase all you like, no problem at all

5.6 Deconstructing the essence of the entire collaboration model#

Everyone has a complete object database and pointer system locally. The essence of collaboration is syncing objects and aligning pointers between different machines. A conflict is the natural result of pointers not lining up; a rejected push is the remote protecting history

6. Conclusion: one idea, four implementations#

Back to the question at the very beginning:

Without a central authority, how do you get people distributed around the globe to agree on the same data?

This is a question that has recurred over and over for decades. People in different fields, in different ways, gave variants of the same answer

6.1 The underlying idea: content addressing + hash chains#

All these systems are built on two primitives:

  1. Content addressing

Instead of locating data with “this file is in the 5th directory of the 3rd server”, locate it with the hash of the data content itself

The hash is the address, and the address is the content’s fingerprint

  1. Hash chains

Each node contains the previous node’s hash, forming a chain

Change any node on the chain and the hashes of all subsequent nodes change. The chain’s integrity can be independently verified by anyone, with no need for an authority to vouch for it

These two primitives combined solve the same problem:

Let data prove its own innocence

Sound familiar? web3? No no no — this idea can be traced back to the 1970s

6.2 1979: Merkle Tree#

Ralph Merkle proposed this data structure in his doctoral thesis

The original goal was very plain: how do you efficiently verify that one piece of data in a large batch hasn’t been tampered with?

The solution:

Put all the data in leaf nodes Each parent node stores the combined hash of its left and right child nodes Compute all the way up to the root node

The root hash represents the state of the entire tree. To verify that C hasn’t been tampered with, you only need to provide D, hash AB and the root hash, without exposing the contents of A and B

This is called a Merkle Proof, with a verification efficiency of O(log n)

It was a purely cryptographic concept, with no concrete application at the time

6.3 2005: Git#

Linus independently arrived at the same structure to solve the code collaboration problem

Git’s object model is exactly a Merkle Tree:

PRTCL // PLAINTEXT
commit
└── tree(root hash)
├── blob
├── blob
└── tree
└── blob

Then parent pointers string multiple trees into a timeline

Git’s contribution was turning the Merkle Tree from a static data structure into a version history with a time dimension

6.4 2008: Bitcoin#

Satoshi Nakamoto faced essentially the same problem as Linus:

Without a central authority like a bank, how do you get people around the world to agree on who owns how much money?

His solution borrowed the same idea:

Each block contains:

PRTCL // PLAINTEXT
block {
transactions_root: the Merkle Tree root hash of all transactions
prev_block_hash: the hash of the previous block
nonce: proof of work
}

prev_block_hash is Git’s parent, stringing blocks into a chain

Change any historical block and the hashes of all subsequent blocks change, and the whole network notices immediately

Bitcoin’s contribution was adding proof of work (i.e. mining) on top of the hash chain, solving the question of who has the right to write new blocks

Git doesn’t need to solve that problem, because Linus himself is the final authority (lol)

6.5 2014: Ethereum’s MPT#

The state Ethereum needs to store is far more complex than Bitcoin’s — not just transaction records, but every account’s balance, the state of smart contracts, code…

A plain Merkle Tree isn’t efficient enough for lookups, so Ethereum combined it with a Patricia Trie (compressed prefix tree) and invented the Merkle Patricia Trie

PRTCL // PLAINTEXT
MPT = Merkle Tree(tamper-proof)
+ Patricia Trie(efficient lookup)

The Patricia Trie addresses by path; for example, the account address 0x1a2b3c... is a path on the tree, and following it finds that account’s state

Each Ethereum block has three MPTs:

PRTCL // PLAINTEXT
state_root ← global account state
transactions_root ← all transactions in this block
receipts_root ← the execution results of all transactions

Compared with Git:

PRTCL // PLAINTEXT
Git's tree object ≈ an MPT's intermediate nodes (path-addressed)
Git's blob object ≈ an MPT's leaf nodes (the actual data)
Git's commit ≈ an Ethereum block (state snapshot + pointer to the previous)

6.6 2015: IPFS#

The problem IPFS wants to solve is:

HTTP is location-addressed: if the server dies, the data disappears. Can we make data content-addressed, so that as long as someone stores it, anyone can retrieve it?

Each IPFS file is split into blocks, each block hashed, and the directory structure is also organised as a Merkle DAG (directed acyclic graph):

PRTCL // PLAINTEXT
directory hash
├── hash of file A
│ ├── hash of block 1
│ └── hash of block 2
└── hash of file B

You request a file by giving the root hash, and IPFS fetches it back from any node on the network that stores the data, verifying integrity with the hash

Compared with Git it’s almost one-to-one:

PRTCL // PLAINTEXT
Git blob ≈ IPFS data block
Git tree ≈ IPFS directory node
Git remote ≈ IPFS network node

IPFS’s author said outright: IPFS is just Git’s object model extended to the entire internet


Their core ideas are exactly the same; the only difference is:

Each had different additional problems to solve, which led to different extensions on top of the hash chain

Finally let’s go back to 2005, when Linus wrote Git in ten days. He didn’t foresee blockchains, didn’t foresee IPFS

He had just thought one question through clearly:

Trust shouldn’t depend on authority; it should depend on mathematics

That one insight, over the following twenty years, was repeatedly rediscovered by people in different fields, and grew into completely different things

Chongxi
CHONGXIAuthor
Steam—
Spotify—
Published at:2026.03.31
Classified
Distribution Notice

What Even Is Git? Git's First Principles and the History of Its Birth, Explained in One Article

Author CHONGXI/2026.03.31/CEPATO-2026-0331-WHATIS

This work is licensed under CC BY-NC-SA 4.0. Redistribution, remixing and adaptation are allowed for non-commercial use, provided the same license is used and the original author is credited.

SubscribeDonate
Discussion
Archive_Ref
Copyright © 2011–2026 Chongxi & CEPATO
Powered by Astro & CEPATO-NEXT
StatusNon-Collaborative_Entity // CEPATO_NEXT_v27.0

Type a keyword to search