While I was a bootcamp student, no subject more consistently caused angst among the members of my cohort than the Git workflow. We had only touched on the absolute basics of Git in our coursework, and we were all consequently varying degrees of bad at it when it came time to work on projects together. It was almost an inevitability that, during project weeks, each group would at one point or another hopelessly wreck their master branch and have to turn to either Stack Overflow or an instructor for help untangling the mess. What I’m realizing nearly two years after the fact is that the problem was not so much that we didn’t know enough Git commands (or the right commands), but that none of us had a strong understanding of what a Git repository even is. When you see how a repository is stored on your computer and how it keeps track of changes in your project files, it’s actually remarkably easy to understand what’s going on and how to work harmoniously with it.
When you initialize a repository in a project directory, what exactly happens?
Beyond the initial message that an empty Git repository has been initialized, if you type
ls in the command line, nothing appears to have changed. If you show hidden files with
ls -a, however, you can see that there is a hidden folder called .git that’s been created. This is where all of the changes to your repository are tracked. Let’s take a look at what it contains in the finder:
There’s too much to cover here in one article, so for our purposes I want to focus on the HEAD file and the refs folder.
If you’re unfamiliar with what HEAD refers to in a Git repo, it’s a pointer to your current working branch. If you’re working on a branch called “development,” for instance, HEAD will point to the development branch. The way that the .git folder stores this awareness of where you’re currently working is as straightforward as can be, with a single line of text in the HEAD file. If I open the HEAD file and I’m currently working on the development branch, this is what I see:
The file contains nothing else. If I switch back to the master branch, the file is rewritten as follows:
Which brings us to the other feature I wanted to discuss, the refs folder. As you can see in the above image, the ref stored in HEAD is pointing to a relative path: refs/heads/master. Sure enough, if we go back to the finder and open up the refs and heads folders, this is what we see:
There are files for both development and master, which are the only two branches I’ve created thus far in my repository. So HEAD is pointing to the file for master. If I create more branches, Git will create more files in this folder. Let’s take a look at what the master file contains:
Once again, it’s just a single line of text, and this one may look familiar to you if you’ve ever looked closely at a commit. Every time you commit changes to a Git repo, Git automatically generates a random and unique identifier called a commit hash, which you can see below in yellow after the word “commit”:
As you may have already noticed, the commit hash in the above image identically matches the one that’s been stored in the master file. So each of the branch files in the refs folder is responsible for storing exactly one thing, the commit hash of your most recent commit to that branch. This is how Git knows what state your project should be in when you switch branches, by retrieving the state of your last commit. That’s it!
When you see your working branch name printed in red text and parentheses in the command line, there’s something misleadingly magical about it. You know it exists somewhere but not necessarily where it is or what it is in its entirety. This is what can make Git mistakes so scary. Git has a strong, non-negotiable sense of what each of your files should contain based on your last commit to a particular branch, and if that sense clashes with your own, it can lead you to panic and think your work has been lost. In actuality, Git is mostly writing and saving single lines of text to files in the .git folder so it can retain and present you with the relevant information for your current context. If you’ve committed something you’ve written, Git maintains a record of it, and it can be retrieved — usually with relative ease.
There’s obviously a lot more to explore with regard to how your repo is stored in the .git folder and with Git in general, and I encourage you to do so, but my hope is that even with these two basic examples you’re beginning to see that Git isn’t scary or magical, and that its core functionality is built around writing simple files to your computer and retrieving information from them as needed. It’s amazing to me that such a powerful and pervasive tool is as elegant and comprehensible as it is when you scrutinize its individual actions.