Lessons 1/4 · 25 min
Git history: commits, branches and controlled rewriting
A starting question
Two students work on the same file. One sends it to the other by messaging; the other replies with "final version 2". Three days later nobody knows which version is right. Git solves this problem: it keeps the history of each change, who made it, when, and why.
What a commit is
A commit is a snapshot of all the project's files at a given moment, with: an author, a date, a message, and a link to the parent commit(s). Git identifies each commit by a hash computed from all this content, including the parent's hash. Historically this hash is a SHA-1; Git can also work with SHA-256 (see the note below).
As with a blockchain, changing an old commit changes its hash, hence that of all its descendants. In the hash tool, change one letter of a text and watch: the hash is completely different. In the chain explorer, edit block 2: all later ones "break". It is the same idea.
Two honest differences from a blockchain:
- There is no consensus. Everyone owns a full copy and can rewrite their local history. The project's "truth" is whatever the team agreed to treat as the reference repository (usually the
mainbranch of the main repository). - A Git hash identifies content; it does not prove who wrote it (except through commit signing, see lesson 4).
The three areas
working directory --git add--> index (staging area) --git commit--> repository (history)git statusshows what changed and what is ready to be committed.git add filechooses what enters the next commit (one commit = one idea, not everything you touched).git commitcreates the snapshot.
Branches: labels that move
A branch is simply a pointer to a commit. Creating a branch copies nothing:
git switch -c feature/fees # create and move to the branch
git log --oneline --graph --decorate --allExample output:
* 9f3c2ab (HEAD -> feature/fees) Add fee estimate helper
* 41d7e90 Fix typo in README
| * c0a11fe (origin/main, main) Update CI node version
|/
* 7b2e5d1 Initial importReading: from the initial commit 7b2e5d1, main advanced by one commit (c0a11fe) and feature/fees by two. HEAD is the branch you are on. origin/main is what you know of main on the server as of the last git fetch.
Good commit messages
A message is read months later by someone searching why a line exists. A widespread convention (not a law):
Reject negative amounts in transfer()
A negative amount credited the sender. Validate amount > 0
and add tests for -1 and 0.A short imperative subject (about 50 characters), a blank line, then the why. A message like "fix" or "WIP" teaches nothing. A commit mixing three subjects cannot be undone cleanly.
Controlled rewriting
Git lets you rewrite history: git commit --amend (the last commit), git rebase -i HEAD~3 (combine, rename, reorder three commits). Every rewrite creates new commits with new hashes. Golden rule:
- Rewriting is safe as long as the commits are yours alone (not yet pushed, or on your pull-request branch that nobody else uses). To push after rewriting:
git push --force-with-lease(more cautious than--force: it refuses if the remote branch has moved since your last fetch, for instance because someone else pushed to it; an automaticgit fetchby your editor can however update your view and defeat this protection). - Rewriting is forbidden on a shared branch (
main): your colleagues built on it. - To undo a commit already shared, use
git revert <hash>: it creates a new commit that cancels the old one, rewriting nothing.
Safety net: git reflog lists the places HEAD has been, even after a rewrite, which generally lets you recover a "lost" commit for some time. git reset --hard, however, permanently discards uncommitted changes.
What this does NOT guarantee
- A clean history is not correct code: it says what changed, not that it works.
- Git does not prove the author's identity: a commit's name and e-mail are fields anyone can write.
git revertdoes not erase the trace of a secret or a mistake: the old commit stays in history.
Recap
A commit = a snapshot linked to its parent by a hash; a branch = a pointer. Rewrite only what is yours alone, and use revert for what is shared.
Interactive tool
SHA-256 digest
Type some text: the digest updates on every keystroke. Compare with a second text to see the avalanche effect.
Digest (64 hex characters)
…Digest of the second text
…Differing characters are underlined and bold. Text is UTF-8 encoded with no space or newline added.
Interactive tool
Blockchain explorer (toy)
Five blocks linked by their digests. Edit a block's data: its digest changes, the next block's link breaks, and every later block becomes invalid.
Building the chain (mining 5 blocks)…
This is a toy. A block's hash is SHA-256 of "index|previous hash|data|nonce"; difficulty is only two leading hex zeros (about 256 attempts). A real chain requires astronomically more work and a network of nodes that each verify the chain.
