Changing the Primary Git Branch
Like I mentioned back on Monday, I decided to spend some time over the last week updating the repositories I use regularly to use a main
branch instead of master
, since the latter has unpleasant an alienating connotations.
Before you tell me that it âobviouslyâ derives from a different meaning, keep in mind that git takes a lot of its ideas from 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,â I have some awful news for youâŚ
Similarly, if youâre going to ask why itâs suddenly importantâŚthatâs 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 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. Hereâs the final version for GitHub, in case anybody wants to play along.
#!/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. I hate the syntax, but 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 cases to compare the names.
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
, or something else entirely? When I change branches (not that I have many public-facing branches), muscle-memory is still git branch m
and then let 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.
However, there are still two (manual) steps remaining. Over 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, 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.
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 it never explicitly references the master
branch in any of its work.
Advanced
It finally dawned on me that this should probably be handled through GitHubâs API, so that the entire experience is just running a script. I didnât feel like digging into it for this post, because learning how to make and handle API calls takes this 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.
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.
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.
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 wonât work for a clone, because that work 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 something like this.
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. Instead, you need something more streamlined, like the following.
#!/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, unlinks the upstream repository, links the new branch upstream (almost as above, though not quite), and finally updates the cloneâs local default branch.
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.
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.
git config --global init.defaultBranch main
When Git 2.28 comes to your platform (Ubuntu is still on v2.25, for example), 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 by an anonymous photographer, released under the terms of CC0 1.0 Universal Public Domain Dedication.
By commenting, you agree to follow the blog's Code of Conduct and that your comment is released under the same license as the rest of the blog. Or do you not like comments sections? Continue the conversation in the #entropy-arbitrage chatroom on Matrix…
Tags: techtips programming git