All posts
·10 min read· git· mental-model· beginners· learning

What I got wrong about Git for years

Six things I believed about Git that were just wrong — commits as diffs, rebase as dangerous, branches as heavy. Fixing the mental model is what finally made Git click.

I used Git for years before I actually understood it.

I could commit, push, pull, and make a branch. I could copy-paste my way out of most messes. But I didn't have a model in my head for what Git was doing. So every time something went sideways, it felt like luck whether I'd get out of it.

What finally fixed that wasn't learning more commands. It was unlearning a handful of things I believed that were simply wrong. Here are the six that mattered most. If you hold any of them too — and most self-taught Git users do — fixing them will do more for you than memorising another twenty flags.

1. I thought a commit was a diff

This is the big one. I thought a commit stored the changes — the lines I added and removed. A little patch, stacked on the patch before it.

It isn't. A commit stores a snapshot of your entire project at that moment. Every file, as it was. Git's own documentation is explicit about this: "Git thinks of its data more like a series of snapshots of a miniature filesystem." (Pro Git, "What is Git?")

Git is smart about storage — files that didn't change between commits aren't duplicated, they're referenced. But the model is snapshots, not diffs. The diffs you see in git show or git log -p are computed on the fly by comparing two snapshots. They aren't what's stored.

Once this clicked, half of Git stopped being mysterious. Why can you check out any old commit and get the whole project back? Because each commit is the whole project. Why is branching cheap? (More on that below.) Because a branch doesn't copy anything — it points at a snapshot that already exists.

If you only fix one belief from this list, fix this one.

2. I thought rebase was dangerous

I avoided git rebase for a long time. I'd read scary warnings: "rebase rewrites history", "never rebase", "you'll lose work". So I stuck to merge and felt vaguely guilty about my messy history.

Here's the truth I wish someone had said plainly: rebase is completely safe on commits you haven't shared yet.

The danger of rebase is exactly one thing — rewriting history that other people have already pulled. If you rebase a branch someone else has based work on, you force them into a painful reconciliation. That's the whole warning. (git-rebase docs — "Recovering from upstream rebase".)

But your own local feature branch, the one nobody else has touched? Rebase it freely. Reorder commits, squash them, clean up messages. You're rewriting history that only exists on your machine. Nobody is affected. And if you mess it up, the reflog has your back (see #6).

"Don't rebase shared branches" is good advice. "Don't rebase" is not — it just keeps you scared of one of Git's most useful tools.

3. I thought git pull was always safe

git pull felt like the safe, simple way to get updates. It's not simple, and it's not always safe.

git pull is two commands in a trench coat: git fetch followed by git merge (or git rebase, if you've configured it). (git-pull docs — "git pull runs git fetch ... and then ... git merge".)

That hidden merge is where the surprises live. If your local branch and the remote have both moved on, pull creates a merge commit you didn't ask for. If you have uncommitted changes that conflict, it stops half-finished and leaves you confused. The "simple" command did two things, and the second one had opinions.

What changed for me was separating the steps. git fetch first — that only downloads, it never touches your working files, so it's genuinely always safe. Then I look at what came in (git log HEAD..@{u}), and then I decide: merge or rebase. The decision is mine, made on purpose, instead of baked into a command I ran on autopilot.

4. I thought branches were heavy

I came to Git after a brief, unhappy time with older version-control tools where creating a branch was a big deal — slow, space-hungry, something you did a few times a year.

So I hoarded branches. I'd do too much work on one branch because making a new one felt expensive.

A Git branch is a file containing one line: the 40-character hash of a commit. That's it. Creating a branch writes 41 bytes. It's instant, and it costs nothing. (Pro Git, "Git Branching in a Nutshell" — "a branch in Git is simply a lightweight movable pointer".)

Branches are so cheap that the right habit is the opposite of hoarding: make one for every small thing. Trying an idea? Branch. Reviewing a colleague's work? Branch. Worried a change might not pan out? Branch — if it doesn't work, you just delete the pointer and the snapshots get cleaned up later. There's no cost to being generous with them.

5. I thought HEAD meant "the latest commit"

I assumed HEAD was Git's word for the newest commit — the tip of everything.

HEAD means "where you are right now". Almost always, it's a pointer to the branch you're currently on (which in turn points at a commit). It's not "the latest commit in the repo" — it's "the commit your working directory currently reflects". (Pro Git, "Git Branching".)

This sounds like a small distinction. It isn't. Once I understood HEAD as a cursor — you are here — a whole family of commands stopped being scary. HEAD~1 is "one commit back from where I am". git reset --hard HEAD~3 is "move my current branch back three steps". "Detached HEAD" stopped being an error message I feared and became a plain fact: I'd pointed HEAD directly at a commit instead of at a branch, so I was standing on a commit with no branch name attached.

Git tells you where HEAD is all the time. I just hadn't understood what it was telling me.

6. I thought a commit could be truly lost

The scariest belief, and the one that made me timid. I thought a bad reset, a deleted branch, or a botched rebase could destroy my work for good. So I treated history as fragile and avoided the commands that touch it.

Almost nothing is truly lost. Git keeps a journal called the reflog that records every time HEAD moved — every commit, checkout, reset, and rebase. The commits those entries point to survive in the object database for a while even after nothing else references them: roughly 90 days for commits still reachable, and 30 days for unreachable ones, before garbage collection is even allowed to remove them. (git-reflog docs, gc.reflogExpire config.)

That means a "lost" commit is usually just a git reflog and a git reset --hard <sha> away. I wrote a whole post on exactly how to do that recovery — it's the single highest-ROI thing I learned in Git.

Knowing the reflog exists changed how I work. I stopped being precious about history. I reorder, squash, and reset freely now, because I know the footprints are still there if I need to walk back.

Common myths

Myth: "You have to understand Git's internals to use it well." Mostly false. You don't need to know how packfiles work. You do need the right mental model for a handful of everyday things — commit, branch, HEAD, reflog. That's six ideas, not a computer-science course.

Myth: "Merge is safe, rebase is dangerous." Half true, stated misleadingly. Both are safe on your own unshared work. The only real rule is: don't rewrite history other people have already pulled. That applies to any history-rewriting command, not just rebase.

Myth: "If git status looks scary, I probably broke something." Usually false. Most of the time git status is telling you exactly what state you're in and what to do next — it even suggests commands. The fear comes from not having the model, not from anything being broken. Read it slowly; it's on your side.

The pattern

Looking back, none of my problems were about commands. They were about the model. I'd memorised what to type without understanding what it did, so I couldn't reason about anything new.

That's the whole reason I built GitFlow: a place to practise on a real Git engine, with a live commit graph that shows you the snapshots and pointers moving as you type — so the model builds itself in your head instead of staying abstract.

If you take one thing from this: stop collecting commands. Build the model. Six ideas — snapshots, cheap branches, HEAD-as-cursor, safe-local-rebase, the two-step truth of pull, and the reflog safety net — and Git stops feeling like luck.

What to read next

If you want the model to build itself instead of reading about it, the Object Model lesson opens a live terminal where you can watch snapshots and pointers move in two minutes.