Git commands used to feel like spells to me. Say the right words and it works. Say the wrong ones and something breaks, and you have no idea why.
What finally fixed that wasn't another cheat sheet. It was seeing what each command does to the picture. So I built a page for myself that draws it, and then I made it public:
It's free, has no sign-up, and works on a phone. The source is on GitHub under the MIT license.
The whole idea: four things
Almost everything in Git is a combination of four ideas:
- Commit. A snapshot of every tracked file, plus the ID of its parent. Arrows point backwards: a child knows its parent, never the other way round.
- Branch. A sticky note holding one commit ID. Committing moves the note forward. Creating a branch copies nothing.
- HEAD. "You are here." It normally points at a branch, and that branch is the one that moves when you commit.
- origin/main. Your repo's memory of where the server's main was the last time you talked to it. It goes stale until you fetch.
Once you see that, a lot of scary commands turn out to be "move a sticky note". git reset --soft HEAD~1 moves a branch back one commit. git switch moves HEAD. git branch adds a note. That's it.
The playground
The main part of the site is a playground with 12 scenarios: commits and branches, merge, rebase, amend and squash, reset and reflog, committing to the wrong branch, detached HEAD, cherry-pick and revert, stash, tags, fetch/pull/push, and force pushing.
You press Next (or →) and watch which pointer moves. Each step shows the real command, what Git prints, and a short explanation.
A few things I added because they're what made Git click for me:
Faded commits. When a commit becomes unreachable (after a reset, rebase or amend), it doesn't vanish. It fades out, because Git still has it and git reflog can bring it back. Seeing that took away most of my fear of "losing work".
Predict mode. Before each step, you tick what you think the command will change: HEAD, a branch, a new commit. Then you see if you were right. It also catches the most common misunderstanding. When you commit, HEAD doesn't move. The branch moves, and HEAD comes along with it.
Click any commit to see its names. Click a commit and it tells you everything that reaches it, like HEAD~2, feature~1 or main^2 (the second parent of a merge). There's also a git log A..B picker that highlights exactly which commits are in a range, which is what main..feature means.
Merge vs rebase, side by side. The same diverged history, combined both ways at once. Merge keeps history as it happened and adds a commit with two parents. Rebase gives a straight line, but replaces your commits with new ones. You can see why "don't rebase what you've already pushed" is a rule.
Where your changes actually live
There's a separate demo for the working folder, the staging area and the repository. You edit, git add and git commit two files, and watch git status change.
One thing I didn't know until I built this: git restore app.js doesn't take a file back to the last commit. It takes it back to the staged version. If you git add a change and then keep editing, git restore throws away only the edits made after the add. The staged change survives.
When something goes wrong
The section I'd have wanted most when I started is called "Oh no, I…". It covers mistakes like these:
- …committed to the wrong branch
- …need to undo a commit that's already pushed
- …lost commits after a reset or rebase
- …got my push rejected
- …committed a password or API key
Each one has the fix and, where possible, a link straight to the playground step that shows it happening.
There's also a merge conflict you resolve yourself. You can try committing too early to see Git's real error, or stage the file with the <<<<<<< markers still in it. Git won't stop you from committing those, by the way, which surprises a lot of people.
A small real-life example
While publishing the latest version of the site, I merged my pull request with GitHub's squash and merge button. Then, locally:
git branch -d feature/more-interactivity # error: The branch 'feature/more-interactivity' is not fully merged. But it was merged. The squash created a brand-new commit on main, so none of my branch's commits are in main's history, and Git can't tell they hold the same changes. After checking that git diff main feature/more-interactivity was empty, I deleted it with git branch -D.
Once you think of branches as sticky notes and commits as snapshots with parents, that error makes sense.
Also on the site
- Under the hood: blobs, trees and commits, what's inside
.git, and a box that computes a real Git blob hash in your browser (it matchesgit hash-object). - Commands worth knowing: searchable, filterable by what each one does ("moves a pointer", "replaces commits", "changes your files"…), with copy buttons and a printable cheat sheet.
- Old
checkout→switch/restore, for when a tutorial uses the old commands. - Settings worth changing once, like
pull.rebase,push.autoSetupRemoteandmerge.conflictStyle zdiff3. - A glossary, plus light and dark themes.
How it's built
It's a single static page with no framework and no build step: HTML, CSS and plain JavaScript. The commit graph is a small SVG renderer. Each scenario is a list of commands, and each command changes a tiny model of a repository (commits, refs and HEAD). The graph then animates from one state to the next. It's hosted on Vercel.
Try it
My suggestion: open the playground, turn on Predict mode, and go through "Reset & reflog" without peeking. If any step surprises you, that's the bit of Git worth learning next.
If something is unclear or wrong, or there's a scenario you'd like to see, tell me in the comments or open an issue on GitHub.





