Jun 9, 2020 Changing the default branch for new Git repositories

The software community has been moving away from master-and-slave metaphors—slowly, and not without resistance—for some time now, but one that’s mostly escaped scrutiny until recently is Git’s default master branch.

As far as I could tell, most of the discussions on the topic I could find online seemed to consist mostly of white people arguing with other white people about how non-white people do or should feel. When I recently tweeted about this, the two Black folks who responded both said that despite not being paired with the term “slave”, the “master” branch does make them feel uncomfortable.

Separately, I learned that—contrary to what white dudes, summoned to the replies like flies to honey, will tell you whenever this comes up—the term’s usage in Git seems to have its origin in a master-and-slave metaphor used in BitKeeper.

It’s also just not a very good name. main is a good one-size-fits-all replacement name, but other alternatives are more descriptive: for instance, if you’re building a hosted service, and you run automated deploys when your main branch changes, you might call it production.

In any case, I’m far from the only person noticing this! Fellow Adelaidean Stuart Jones and prominent Microsoftie Scott Hanselman both published posts on this exact topic yesterday alone. Both posts cover renaming master to something different in an existing repo, but new repos will still default to master.

Git doesn’t have a configuration option to override this default, but there is a workaround!

How to do it

  1. Create a new directory somewhere. For this example, I’ll use ~/.config/git/template.
  2. Inside that, create a new file called HEAD, with the contents ref: refs/heads/main. (You can substitute main here for whatever you’d like your new default to be.)
  3. Set Git up to use your new template: git config --global init.templateDir ~/.config/git/template

What it does

A lot of Git commands let you use hooks to extend their behaviour, but git init isn’t one of them. Instead, git init lets you set a template directory, the contents of which are copied over the top of the newly-created .git directory once initialisation is done.

(Template directories are also used when you git clone an existing repository, but the HEAD file gets overwritten afterwards. This means our change doesn’t affect clones, only brand new repositories, which is what we want.)

The second part of the puzzle is Git references, which are almost always referred to by the abbreviation ref. A ref is a name that points to a commit, and they’re stored as text files with the ref name as the file name, and the commit hash as the file’s contents.

Branches and tags are both kinds of ref! Open any Git repository’s .git folder, and you’ll find that in .git/refs/heads there is one file named for each branch. Open one of those files, and you’ll see the commit hash of the newest commit on that branch (which is called the branch’s “head”, hence the directory name). The .git/refs/tags directory contains all of the repository’s tags, stored in the same manner.

These are all ordinary refs, but there’s another kind of ref called a symbolic ref. Instead of pointing to a commit, symbolic refs point to another ref, which points to a commit. (If you’re familiar with symbolic links, symbolic refs are the Git equivalent, which is why they’re called that.) Symbolic refs are text files too; instead of the commit hash, their content is ref: followed by the ref that they point to.

At this point, you can probably tell where we’re going! There is a special ref in a git repository called HEAD, which points to the commit that’s currently checked out. It’s usually a symbolic ref, pointing to the currently checked out branch.

The final puzzle piece is this: When you run git init, Git doesn’t actually create the master branch at that point. It just creates the HEAD symbolic ref, deliberately pointing it to a branch that doesn’t exist yet. When you create your first commit, git commit notices that HEAD is pointing to a non-existent branch, and creates it. In our workaround, we just replace HEAD so that it points to a non-existent branch with a different name.