Changing the Primary Git Branch
Tags: programming, git, automation
Published: 2020-07-30 12:52:11

---
title: Changing the Primary Git Branch
published: true
date: 2020-07-29 10:55:02 UTC
tags: programming,git,automation
canonical_url: https://john.colagioia.net/blog/2020/07/29/gitmain.html
---

As a quick note, I [released this post](https://john.colagioia.net/blog/2020/07/29/gitmain.html) on my blog, this morning, so it can get to be (as I tend to be) a bit rambling.  Oh, and the original text is [on GitHub](https://github.com/jcolag/entropy-arbitrage) (licensed CC-BY-SA), so if anything seems muddy, by all means:

 * Leave a comment here,
 * Leave a comment on the blog,
 * File an issue on GitHub, or
 * Add a pull request!

{% github jcolag/entropy-arbitrage no-readme %} 

* * *

Recently, I decided to spend some time updating the repositories I use regularly to use a `main` branch instead of `master`, since the latter has unpleasant an alienating connotations.

![Branches](https://dev-to-uploads.s3.amazonaws.com/i/0chbwszqauyyvhajb59p.png "Branches")

Before you tell me that the term “obviously” derives from some different meaning, keep in mind that [git](https://git-scm.com/) takes a lot of its ideas from [BitKeeper](https://en.wikipedia.org/wiki/BitKeeper), which includes the concept of a master branch with slave branches. Likewise, if you’re going to argue that it’s from “master copy,” well, I have some awful news for you…

Similarly, if you’re going to ask why it’s suddenly important…that’s actually a good question, because this sort of terminology should _never_ have been acceptable, and it’s embarrassing that we need to be reminded that our choice of words has consequences. But since everybody is thinking about it now, it’s a good time to do it, rather than sheepishly operating the minority of repositories that didn’t bother.

In other words, the best time to fix a mistake is always **now** , because you can’t fix anything before now and waiting until later just leaves the mistake where it is.

## Altering the Branch

Plus, regardless of the intent, it’s _still_ an uncomfortable term to have around, and it can easily give the impression that the project (and the industry as a whole) isn’t ready to welcome certain kinds of people. So, I’m going to change it where I can. Because I have dozens of repositories floating around, I decided to get it right once and then move the information to a script to make it reproducible when I stumble on another forgotten repository. Here’s the almost-final version of the script for GitHub, in case anybody wants to play along.

```sh
#!/bin/sh
branchpath=/settings/branches
url=$(grep 'url = ' .git/config | \
  cut -f2- -d'=' | \
  sed 's/^ *//g' | \
  sed 's/\.git *$//g' )
if case $url in git@*) true;; *) false;; esac
then
  url=$(echo $url | \
    sed 's/:/\//g' | \
    sed 's/^git@/https:\/\//g')
fi
git branch -m master main
git push -u origin main
echo Go update the default "${url}${branchpath}"
echo Then run:
echo git push origin --delete master
```
It doesn’t have any error checking, so don’t run it outside your repository’s local root folder, where you have administrative control over the remote/upstream repository.

The `url` variable digs into the git configuration file to find the `remote` repository. If the `url` we find starts with `git@`, modify it to a real GitHub URL instead of GitHub’s `git@github.com:name/project.git` format. All the pieces are there, of course. The `case` syntax looks like a mess, but it works without any special exceptions to compare the names and should be completely portable across shell scripts.

Then, we have two `git` commands. The first moves (`-m`) or renames the `master` branch to `main`. Next, we `push` the new branch and set the upstream (`-u`) to point to it. At this point, the local repository only has a `main` branch and is set to work exclusively with the remote `main` branch.

Why `main` and not `trunk`, `primary`, `production`, or something else entirely? When I change branches (not that I have many public-facing branches), my muscle-memory is still for typing `git branch ma` and then letting the shell handle auto-completion. You can call yours what you want, obviously, and even update the script to default to a name if you don’t provide it with one without too much trouble.

However, there are still two manual steps remaining. Over on the GitHub page, the default branch still reads **master**, so someone needs to change it by hand. So, that `url` work the script did earlier pays off here, producing a link to `https://github.com/NAME/PROJECT/settings/branches`, where the user can update the default. Obviously, I make no claims about the URL being correct for any host other than GitHub. (I really should start looking at GitLab; they seem like a nice company that people should pay some attention to.)

Once that’s done, you can delete the `master` branch entirely and never worry about it again…except for the rest of this post, because there are still some edge cases and improvements.

I’m not currently running any CI/CD, but if you choose to work with this script and do, you should take a minute and make sure that the tool never explicitly references the `master` branch in any of its work, at this point.

### Advanced

It eventually dawned on me that the big manual step (changing the default branch) should _probably_ be handled through GitHub’s API, so that the entire experience is just running a script. I initially didn’t feel like digging into it for _this_ post, because learning how to make and handle API calls takes this project from a “quick script” to a full application that needs to handle remote authentication and all the possible error states. So, I wasn’t going down that path…until I remembered that GitHub has [a CLI tool](https://cli.github.com/).

Installing that takes care of the “application” part of this. And with some research, I was able to work out how change the default branch _entirely_ through the tool.

```console
gh api repos/the-user/the-repository -X PATCH
  -F name="the-repository" \
  -F default_branch="main"
```

Obviously, change `the-user` and `the-repository` (both instances) to yours. But now that we have this piece of information, we can replace the `echo` lines above with the following.

```sh
user=$(echo "${url}" | cut -f4 -d'/')
repo=$(echo "${url}" | cut -f5 -d'/')
gh api repos/${user}/${repo} -X PATCH -F name="${repo}" -F default_branch="main"
git push origin --delete master
```

We know that a repository URL is going to look like `https://github.com/the-user/the-repository/`, so we pull off the fourth and fifth items, as delimited by slashes. The first is `https:`, the second is empty, and so forth.

Then, we call the GitHub CLI tool. The first time you successfully call it, it will ask you to hit Enter to open GitHub in a browser. There, you can authorize the tool to operate on your behalf. Then, once you’re authenticated, the script continues on to delete the branch.

Except for the one-time authentication step, there are no manual steps. I call that a success.

## Local Clones

The above is all well and good, but it won’t work for an ordinary clone, because the work there is primarily administrative. If you have a clone of a project that has been making changes like this (for example, those of my projects that have migrated over), you’ll see an error something like the following.

```
From url-to-repository
 * [new branch] main -> origin/main
Your configuration specifies to merge with the ref 'refs/heads/master'
from the remote, but no such ref was fetched.
```

The above script won’t work, because it wants to affect the origin, which has already been affected and you might not have access to. Instead, you need something more straightforward, like the following.

```sh
#!/bin/sh
git checkout master
git branch -m master main
git fetch
git branch --unset-upstream
git branch -u origin/main
git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/main
```

This makes sure that you start out on the `master` branch (to make sure that you don’t break a different branch), moves/renames the branch as above, gets the latest commits from upstream, disconnects the upstream repository, links the new branch upstream (almost the same as above, though not quite), and finally updates the clone’s local default branch to point to the right place.

Like above, run it in the clone’s root folder and don’t expect the script to recover easily if there are any errors.

## Never Again…

It would, of course, be nice if we never needed to see a `master` branch again. Most of us don’t yet have the ability to just tell `git` that we never want a repository with one, but we can create a decent alias to use instead of `git init` until we do.

```console
git config --global alias.new '!git init && git symbolic-ref HEAD refs/heads/main'
```

Now, we can call `git new` to initialize our repositories and the alias will create it and set the default branch name.

That’s not an ideal situation, since muscle memory will inevitably type the default command that we can’t override. But as a temporary measure, it should work out for most cases until the latest version of `git` shows up. To prepare for that, you might as well configure things now.

```console
git config --global init.defaultBranch main
```

When Git 2.28 comes to your platform (Ubuntu is still on v2.25, for example, and if you're on a machine that doesn't auto-update git, you're probably always a few versions behind, too), it will pick up that option and all of your new local repositories will have default branches with your custom name.

* * *

**Credits** : The header image is [tree, nature, branch, winter, black and white, plant, trunk, bark, high, monochrome, twig, trees, twigs, woods, branches, tall, monochrome photography, atmospheric phenomenon, woody plant](https://pxhere.com/en/photo/997803) by an anonymous photographer, released under the terms of [CC0 1.0 Universal Public Domain Dedication](https://creativecommons.org/publicdomain/zero/1.0/).