This is episode one of Git Good, Season Two.
There is a word that no developer wants to see in their terminal. Not "error." Not "warning." The word is "fatal." Git uses it constantly. You mistype a branch name: fatal. You try to push before pulling: fatal. You check out a commit instead of a branch and then try to do something Git did not expect: fatal. The word appears in red, usually followed by a sentence that explains nothing, using vocabulary the developer has never encountered. Fatal, colon, and then gibberish.
The last season ended with Git having won. The format wars, the competing tools, the years of skepticism, all resolved in Git's favor. What we did not examine is what victory actually looks like for the millions of developers who now wake up every morning and have to use the thing. It looks like red text. It looks like the word "fatal" for mistakes that are not remotely fatal. It looks like a tool that talks to humans the way a radiologist talks to a scanner. Precisely. Accurately. And in a language the patient cannot understand.
This is a story about the wall. The invisible barrier between knowing a few Git commands and actually understanding what the tool is doing. It is a story about the worst error messages in computing, the people who wrote them, and the people trying to fix them. And it is the thesis statement for everything that follows this season: Git won. Now we have to decide if winning was worth it.
Somewhere right now, a developer is panicking. They just committed something they should not have, or committed to the wrong branch, or pushed a change that broke everything, and they are doing what every developer in this situation does. They are opening a browser and typing "how do I undo" into a search bar.
The most viewed programming question on Stack Overflow is not about algorithms. It is not about databases or memory management or any of the things computer science professors think are hard. It is about Git. Specifically, it is the question "How do I undo the most recent local commits in Git?" That question has been viewed over fifteen million times since two thousand nine. Fifteen million moments of quiet panic from people who use a tool every single day and still do not understand what it does.
And that is just one question. Four of the ten most viewed questions on all of Stack Overflow are about Git. Not about a specific programming language or framework or cloud service. About a version control tool. How do I delete a remote branch. How do I rename a local branch. How do I revert a file. How do I resolve merge conflicts. Millions upon millions of views, each one representing a developer who hit the wall.
Git is not new. It has been around since two thousand five. Developers interact with it more often than they interact with their text editor, their programming language, or their operating system. And yet most of them could not explain what a commit actually is. They have memorized a handful of commands the way you memorize a phone number, without understanding the system behind it. Git add, git commit, git push. The sacred three commands that make code go to the cloud. When something goes wrong, and something always goes wrong, they do not debug. They do not reason about state. They Google. And the cycle repeats.
The error messages are where the wall becomes concrete. Here is what happens when you try to check out a branch that does not exist. You type git checkout followed by a name, and Git responds: "error: pathspec did not match any file known to git." You wanted to switch branches. Git is talking about pathspecs and files. The word "branch" does not appear anywhere in the message. A developer who does not know what a pathspec is, which is most developers, has no idea what just happened.
The newer command, git switch, does slightly better. Use it with a name that does not exist and you get "fatal: invalid reference." The word "fatal" at least tells you it failed. But now the word is "reference." A lot of Git users have no idea what a reference is. They think in terms of branches and tags and commits. "Reference" is the internal abstraction that unifies all three. A perfectly reasonable term if you work on Git itself. Gibberish if you are a frontend developer who just wants to switch to the feature branch.
This pattern runs through almost every confusing Git message. Git talks in its own vocabulary, not the user's. The staging area is called the "index." The current position in history is called "HEAD." A file path is called a "pathspec." Your local copy of a remote branch is called a "remote-tracking reference." Each of these terms is technically precise. Each of them is meaningless to someone who has not read the Git glossary, and almost nobody has read the Git glossary.
Julia Evans, a programmer in Montreal who writes some of the best technical explanations on the internet, cataloged this problem in detail in two thousand twenty-four. She walked through error message after error message, showing how each one uses internal vocabulary where human vocabulary would be clearer. Her suggested fix for most of them was almost embarrassingly simple. Just say "branch" instead of "reference." Say "file" instead of "pathspec." Say "your current branch" instead of "HEAD."
The problem is not that Git cannot communicate clearly. The problem is that Git communicates in a language its users do not speak.
Of all the messages Git has ever produced, none has terrified more beginners than this one. "You are in detached HEAD state." For someone who does not know what HEAD means, this sounds like something has gone catastrophically wrong. Something has been detached. The word HEAD suggests the most important part of something. And the word "state" implies you are stuck in a condition you did not ask for.
What actually happened is mundane. You checked out a specific commit instead of a branch. Your working directory now points directly at that commit rather than following a branch name. This is perfectly normal and useful. You might want to look at what the code looked like three weeks ago. You might want to test an old version. Detached HEAD is fine. Nothing is broken.
But Git does not say "you are looking at a specific commit." It says you are in detached HEAD state, and then prints a paragraph of advice about creating new branches and using git switch with a detach flag, introducing yet more terms the user has never encountered. The advice is technically correct and practically useless for the person reading it, who mostly just wants to know if they have broken something and how to get back to where they were.
This is what Git's advice system does. It was added over the years as a way to give users more context after confusing operations. The intention is good. The execution assumes a level of understanding that the person reading the hints almost certainly does not have. If they understood the hints, they would not need the hints. The deeper irony is that Git knows it has a communication problem. The advice system is proof. Someone looked at "detached HEAD state" and said, this needs more explanation. And then they wrote the explanation in the same vocabulary that caused the confusion in the first place.
In two thousand sixteen, a frontend developer at Etsy named Katie Sylor-Miller did something that would resonate with millions of people. She published a website. Not a tutorial, not a course, not a comprehensive guide. Just a single page with a profane name and a simple premise: you are in trouble with Git, and here is how to get out of it.
The site was called Oh Shit, Git. There is also a version without the profanity for workplaces with content filters. Katie had been keeping a text file on her computer for years, a personal cheat sheet of Git emergencies and their solutions. One evening she wrapped some HTML around it, published it to her server, tweeted a link, and went to bed.
I woke up the next morning and my Twitter had blown up. I was on the front page of Hacker News.
The site went viral because it did something that Git documentation almost never does. It admitted that Git is confusing. The official docs explain commands in terms of other commands, using vocabulary that assumes you already understand the thing you are trying to learn. Oh Shit, Git starts from the opposite direction. It starts from the panic. You just committed to the wrong branch. You need to undo a commit that already got pushed. You accidentally deleted something. Each scenario is a moment of genuine fear, and Katie meets you there with plain language and a solution.
Git does not describe workflows. Git describes internal plumbing commands and everything that it exposes in its API. The whole point of Git is that just about everything can be undone, and just about everything is fixable, and here is how you do it.
But here is the part that cuts closest to the wall. Even after creating this hugely popular Git reference, Katie did not feel like she understood Git. She admitted this openly. It was only when she submitted a conference talk proposal about Git and got accepted that she actually sat down to learn how the tool worked. The pressure of standing in front of an audience forced a depth of understanding that years of daily use had not. She went from Etsy to Square, eventually becoming a Principal Engineer. The site she built in two hours has been translated into twenty-eight languages. She calls herself the Fairy Gitmother. Millions of developers owe her a thank you.
The takeaway is not that Katie was uniquely confused. A senior engineer who used Git daily for years could build a reference used by millions and still not understand the tool. That is not a personal failing. That is a design problem.
If Katie met people in their moment of panic, Julia Evans took a different approach. She wanted to prevent the panic in the first place by giving people a mental model of what Git is actually doing.
Julia publishes small illustrated guides she calls zines, through her company Wizard Zines. They look like comic books about technical topics. In two thousand twenty-four, she published one called How Git Works, and it took her eight months to write.
Eight months for twenty pages might sound excessive until you understand what she was doing. She was not writing documentation. She was building a visual vocabulary for something that has always been explained with words alone. Git is, at its core, a graph. Commits point to their parents. Branches point to commits. The entire history of a project is a structure you can draw on paper, and once you can see it, everything about Git makes sense. Merging is combining two paths. Rebasing is replaying commits onto a different path. A detached HEAD just means you are looking at a specific node instead of following a branch name. But Git's interface is text. The graph that makes everything click is invisible. You have to hold it in your head, and nobody taught you how to build it there. Julia's insight was that the gap between Git's visual reality and its textual interface is where all the confusion lives.
She also identified something specific about Git's vocabulary. Terms like "fast-forward," "remote-tracking branch," and "reference" sound intimidating but describe simple ideas. The status messages Git prints sometimes do not accurately reflect what just happened. When the tool itself is unreliable as a teacher, you need someone on the outside translating.
Julia and Katie even collaborated. The Oh Shit, Git zine combines Katie's panic-first scenarios with Julia's visual explanations. Between them they have probably done more to make Git accessible than any official documentation effort. And neither of them works on Git itself. They are translators, standing between a powerful tool and the people trying to use it, doing work the tool's creators never prioritized.
There is a specific class of Git message that deserves its own special mention, and it involves pronouns. When you are in the middle of a merge and run git status, Git might tell you that a file was "deleted by them." When you are rebasing, the same situation might say "deleted by us." The problem is that "us" and "them" switch meaning depending on which operation you are performing.
In a merge, "ours" means your branch and "theirs" means the branch you are merging in. In a rebase, "ours" means the branch you are rebasing onto and "theirs" means your commits being replayed. So during a merge, your changes are "ours." During a rebase, your changes are "theirs." The same developer, working on the same code, finds their work described by opposite pronouns depending on which command they used.
This is not a quirk. This is an active source of data loss. A developer resolving a conflict during a rebase who picks "ours" thinking it means their own work will accidentally keep the other branch's version and throw away their changes. The terminology is not just confusing. It is dangerous. Julia Evans flagged another related problem. During a rebase conflict, Git shows a message with a commit hash that is not human-readable, and HEAD, which during a rebase does not refer to where you were before the rebase started but to the commit currently being applied, which shifts with every step. The same word means different things at different moments within a single operation.
Evans suggested a fix that seems almost too obvious. Instead of showing commit hashes and the word HEAD, just use branch names. Say "deleted on main and modified on mybranch." The information is available. Git knows which branches are involved. It just chooses to show internal state instead of human context.
If you want to understand the single biggest UX failure in Git's history, look at checkout. The command git checkout does three completely different things depending on how you call it. With a branch name, it switches branches. With two hyphens and a filename, it restores a file to its last committed state. With a commit hash, it puts you in detached HEAD state. Three operations. Three mental models. One command.
This was not deliberate design. It was an accident of history. Checkout was the command that updated your working directory. Switching branches updates your working directory. Restoring a file updates your working directory. Looking at an old commit updates your working directory. So they all ended up in the same command. It made sense from the implementation's perspective. It made no sense from the user's.
The consequences are real. A developer who means to switch branches but forgets the two hyphens can accidentally overwrite a file they have been working on. Git will not warn them. The file is just gone, silently replaced with the version from the last commit. If the changes were not staged or committed, they are lost. There is no undo.
In two thousand nineteen, a developer named Nguyen Thai Ngoc Duy, who goes by the handle pclouds on the Git mailing list, got a proposal accepted. He wanted to split checkout into two new commands. Git switch would handle branch operations. Git restore would handle file operations. Each command would do one thing, with sensible defaults and clear error messages.
The purpose of this command is to avoid the confusion of one-command-do-all git checkout for new users.
The patches landed in Git two point twenty-three in August two thousand nineteen. Both commands were marked "experimental." And here, seven years later, they are still marked experimental. Git checkout has not been deprecated. It will never be deprecated. There are millions of scripts, tutorials, muscle memories, and CI pipelines that depend on it. You cannot take it away without breaking the world. This is the backwards-compatibility tax. Git can add better commands. It cannot remove the bad ones. Every improvement is additive. The confusion does not decrease. It just gets a new layer on top.
The effort to make Git less hostile is not one person's crusade. It is a slow, distributed, slightly uncoordinated push by people who care enough to do the unglamorous work of rewriting error messages and arguing about wording on a mailing list.
Jeff King, who goes by Peff, spent eleven years at GitHub where his job was mainly about improving Git. He is one of the most prolific contributors in the project's history, with thousands of commits. He has laid out guidelines for how error messages should work, but getting consensus in a project with dozens of active contributors and decades of accumulated conventions is slow work.
We have not been very consistent about this. I think laying out guidelines could help, but the real work is in reviewing patches carefully and pushing back when messages are unclear.
Emily Shaffer, a software developer at Google, has worked on Git from a different angle. Her most important contribution might be the "My First Contribution" tutorial, a guide for people who want to start contributing to Git itself. She wrote it with a specific philosophy.
Writing a new contributor document works well if you already do not know what is going into the document. You catch the assumptions because you are bumping into them yourself.
That sentence contains the diagnosis for Git's entire communication problem. The people who write the error messages already know what the error messages mean. They do not bump into the assumptions because they do not share them. Emily also proposed a Git-project-blessed blog for making Git better understood outside of man pages and mailing list archives. An acknowledgment that the tool needed to explain itself to the rest of the world.
Every year, tens of thousands of people graduate from coding bootcamps and enter the software industry. They have learned a programming language, maybe two. They have built projects. They can write code that works. And somewhere in their twelve or sixteen week program, they got maybe one day on Git. Sometimes two. Rarely more.
In that day they learned git add, git commit, git push. Maybe git pull. Maybe git clone. They learned that Git tracks their code and that GitHub is where code lives. They practiced the workflow: make changes, stage them, commit with a message, push to GitHub. If they were lucky, their instructor mentioned branches.
Then they get their first job. And their team uses Git Flow, or GitHub Flow, or trunk-based development with feature flags. Their team has branch protection rules and required code reviews and automated tests that run before any merge. Their team has merge conflicts that span hundreds of lines across dozens of files. Their team has a colleague who insists on rebasing and another who insists that rebasing is dangerous.
The bootcamp graduate has none of the vocabulary for this. They do not know what HEAD means. They do not know what the reflog is, the safety net that records every move and lets you recover from almost any mistake. They do not know the difference between merge and rebase, except that both sometimes produce terrifying red text about conflicts. They do not know that git reset has three modes that do three very different things, and that using the wrong one can make their work disappear.
So they do what every developer in this situation does. They go to Stack Overflow. They find a command that seems to match their problem. They run it. Sometimes it works. Sometimes it makes things worse. Either way, they learn nothing about why. The emergency is over. The confusion remains. And the cycle repeats until they have been a professional developer for three years and still feel like a fraud every time they interact with their version control system.
This is not the bootcamps' fault, not entirely. They are trying to compress an enormous amount of material into a very short time, and Git is not the thing their students are paying to learn. But the result is a generation of developers who think of Git as a necessary evil, a hostile system they must appease with the correct incantation rather than a tool they direct with intention.
Here is the uncomfortable truth that every Git educator eventually confronts. You cannot simplify Git without lying, and the lies will hurt you later.
Take branching. The standard simplified explanation is that a branch is like making a copy of your code so you can work on something without affecting the main version. This is a fine mental model for about six months. Then you encounter a detached HEAD, where you are not on any branch at all, and the copy metaphor falls apart because nothing was copied. A branch in Git is a pointer to a commit. But "a branch is a pointer" is not a useful explanation for someone who does not yet know what a commit means in any meaningful sense.
Or take the staging area. The simplified version is that git add tells Git which files you want to include in your next commit. True enough. But then someone adds a file, edits it again, commits, and finds that the second edit is not in the commit. Because git add stages the file's content at the moment you run it, not the file itself. The simplified explanation was a lie that cost someone twenty minutes of confusion and a frantic trip to Stack Overflow.
This is where reasonable people genuinely disagree. Is the wall a design flaw, or is it an honest reflection of genuine complexity? The case for design flaw is strong. The same operations could be communicated with clearer vocabulary, more consistent pronouns, and error messages in words you understand. The checkout mess was not inevitable. The detached HEAD message could say "you are looking at a specific commit, not a branch." The push rejection could tell you whether your branch is behind or has diverged. These are word choices, not deep technical problems.
But the case for inherent complexity is real too. Distributed version control with content addressing and history rewriting is not a simple idea dressed up in complicated clothing. A merge really does create a new commit with two parents, and the conflicts really do require human judgment. Any simplified explanation will eventually fail the person who believed it. The Git contributors who resist simplifying messages are not being stubborn. Some genuinely believe that using accurate technical terms, even if those terms require learning, is more respectful than using simplified terms that will mislead later.
The counter-argument is that you can be accurate without being obscure. You can say "branch" instead of "reference" without lying about what a reference is. You can say "your changes" instead of "theirs" during a rebase without misrepresenting what is happening. Clarity and accuracy are not opposites. They just require more effort than dumping internal state to the terminal.
There is a hopeful thread in this story. Katie built a site in two hours that has helped millions. Julia spent eight months drawing graphs that make the invisible visible. Duy split a single monstrous command into two sensible ones. Peff and Emily keep filing patches that change one message at a time, replacing "reference" with "branch," adding context where there was none. None of them waited for Git to fix itself. They stood between the tool and the people, and they translated. The wall is still there. But it is getting shorter. And the people climbing over it keep building handholds for the ones behind them.
That was episode one of Git Good, Season Two.
Git switch. Two words that replaced a small identity crisis. Before git switch existed, the command git checkout handled branch switching, file restoration, and detached HEAD creation, three operations that have nothing in common except that they update your working directory. Try switching branches with git switch instead of git checkout. Try restoring files with git restore. Each command does one thing, with clearer messages when something goes wrong. Your muscle memory will resist. Give it a week.