Raimund Krämer

Software Craftsman, Consultant, Coach

We all have to start somewhere. For most of us, our Git journey begins with about 5 commands and the mental model of a file-sharing tool like Dropbox or OneDrive: Upload my files, download my colleagues’ files, maybe resolve a conflict now and then (“use mine”). This is fine, we want to get productive as soon as possible after all.

How many of us got introduced to Git.
(https://xkcd.com/1597/)

The problem is that many developers stop here. While learning more commands has very little benefit once you know those that you need on a daily basis, even just a better understanding of Git’s underlying model would provide many developers with more confidence and safety while using Git. Their mental model of branches, commits, and especially operations like merging and rebasing is often very different from how things actually work, which leads to a lot of cognitive dissonance and even fear when anything at all goes wrong, and the resulting versioning history is mostly accidental.

Following, I will try to describe how I perceive the version control skills one can (or should) have as a developer. Not all of them are specific to Git. The way I see it, they can be roughly divided into 3 stages, where progress is not necessarily linear or even easy to measure but just a general trend. In case you recognize yourself, this model is intended to help with self-assessment and a way forward, not to belittle or offend. I have been in each of these stages as well, and I regularly see them confirmed through interactions with other developers of all experience levels. It’s possible that there are additional stages beyond those three that I have simply not yet recognized because they are more “meta”.

The motto for getting better with Git. The need for a command should be motivated by a higher-level version control or design goal. Memorizing more commands or even using a cheat sheet rarely leads to knowing when to use them.

Stage 1: File Sharing

In my experience, most developers are in this stage for a significant amount of time after being introduced to Git. They are reaffirmed by otherwise experienced developers who have similarly never gone beyond the basics with Git, have credibility due to their perceived seniority, and tell the newcomers that “6 commands are enough, you don’t need more than commit, push, pull, …”.

The mental model of these developers is often heavily influenced by their previous experience with similar tools like Subversion, where many things work quite differently but are mapped onto their current understanding of how Git works. Those who started out with Git and are not influenced by other version control tools instead build a mental model that is closer to file sharing. Since they are taught to “use these 6 commands to sync your files” before they learned about the benefits of a commit history where you can quickly access every old version and even search for specific changes, their whole understanding is centered around the ability to work in isolation from “other developers who could break their version” and about making their work (at some point) available to their colleagues via the central repository. In essence, uploading their code so that others can download it. It is difficult to recognize any kind of intentional workflow or habits in how they use Git, making many of their actions seem, in a way, accidental. They imagine branches as containers of commits or even as copies of the codebase, where merging two branches together somehow copies the code from one branch to the other, and where deleting a branch that another branch builds on causes the space-time continuum to implode. They have huge misconceptions about basic concepts like remote branches, fetch, pull, merge, and rebase. For anything more complicated than what they have memorized they rely heavily on googling, written step-by-step instructions from their project wiki, and asking their colleagues the same things over and over. In case of merge conflicts panic ensues. They live in a comfort zone of anti-patterns and learned helplessness.

Stage 2: The Basics

Stage 2 is the epitome of the pareto principle applied to Git’s feature set. In this stage, developers have the basics more or less down. Unlike stage 1, where only the bare minimum for being able to work with others at all is known and used, these developers now actually have a solid understanding of the basic Git concepts. They easily and routinely resolve merge conflicts. They understand what a commit is and what branches and other refs are, how they can prevent data loss and recover “lost” commits, how the history forms a DAG, and how they can quickly find out who made a certain change and when a change was made and whether a certain change is contained within a given version. Many of them have a strong preference for either merge or rebase for “updating” their branches, treating them as direct alternatives and always using the same. They typically use a branching strategy like Git Flow or GitHub Flow and conventions like conventional commits. Some of them might customize Git with their own command aliases and automate certain workflows with hooks.

Many developers reach this stage at some point once they start digging deeper. It takes relatively little effort once they actually start reading an introduction to Git and unlearn some of their bad habits. In my experience, a significant fraction of the developers are here, although there’s a lot of variance within this stage. The easiest way to get here is by working closely with others that are already at least in stage 2, seeing how they use Git, and asking questions whenever something unfamiliar comes up.

Stage 3: Collaboration and Flow

A developer whom I highly respect once said “If you need to be really good with Git, you’re probably doing something wrong”1, and I agree. This stage is not about even deeper knowledge of the tool and knowing even the most niche commands for every situation. Rather, it’s about how version control fits into the bigger picture, how it is used to achieve higher-level goals. Still, I don’t think it is possible to reach and reliably execute at this stage without knowing the basics from stage 2. To me, getting better with Git beyond a certain point means going beyond knowledge and muscle memory and into how certain practices and habits synergize with each other.

Developers in stage 3 are aware of how work flows through the system and through the processes. They use Git with confidence and intention. They don’t just know how to branch, but are aware of the risks and disadvantages of branching. They know how to scope commits in a way that supports continuous integration, and that continuous integration has more to do with practices and habits than with tooling. Making a trivial change takes minutes, not hours, until it is contained in a potentially releasable build. To them, committing is not just a command that saves their code so that they can upload it to the central repository, but a rhythm of versions, a continuous flow of working software. The resulting history is a searchable, bisectable, simple graph, where each commit is meaningful and accidents are cleaned up before they are published. At any point in time you could ask them what their next commit will contain, and they would be able to tell you in advance how they would word their commit message because the scope of their next commit is an intentional design decision, an intended small increment that motivates a test that necessitates an implementation. This helps them focus on what matters, allowing them to integrate their change within a few minutes to an hour. Merge conflicts are rare and trivial to resolve. When a merge conflict occurs, that’s feedback about the flow within the system rather than a “version control happenstance”. There is never a lot of unused code-inventory on someone’s branch causing rework and delay, instead everyone can use and build on their teammates’ tested code almost in real-time.


  1. I think it may have been Bryan Finster, but I’m not 100 percent sure. The exact words may have been different, but to the same effect. ↩︎