We’ve started creating all new repos with a
main default branch, but there are a number of existing git repositories that still use
master as their default branch name. GitHub has been making a similar transition on their site along with the git community.
Searching online will reveal a lot of articles about how to change the default branch on
github.com or changing the default branch on a local machine, but we have found that making changes to the
origin can cause problems for people who don’t also make certain changes locally. We use some tools which can get confused if
git’s internal files (the
.git directory) are not accurate anymore.
Below I’ll document the steps we take when we change the default branch of a repo. Then I’ll dig into what the symbolic refs are and why they might not get updated automatically if one follows some other online instructions.
How we change the default branch both remotely and locally
github.com UI, we rename the
master branch to
This blog post is assuming the remote is named
origin; if your remote has a different name then you’ll have to substitute it.
We then locally run:
$ git fetch origin $ git remote set-head origin -a $ git branch -m master main $ git branch -u origin/main main
Below I’ll try to explain each command in detail.
git fetch origin
First, we’ve made a change on
github.com so we fetch to update the local
.git objects and refs about the remote. Fetching will add any missing objects into
.git/objects and update the files in
.git/refs/remotes/origin/. We can see how those change by
lsing before and after the fetch:
$ ls .git/refs/remotes/origin HEAD master $ git fetch origin From github.com:shareup/base64url-apple - [deleted] (none) -> origin/master (refs/remotes/origin/HEAD has become dangling) * [new branch] main -> origin/main $ ls .git/refs/remotes/origin HEAD main
We can see the file
master is gone and
main has appeared. There also is the file
HEAD which is curious because there is also a (strange) message from
fetch that “
refs/remotes/origin/HEAD has become dangling.”
HEAD symbolic ref for a remote is “optional” according to the documentation for
git remote, but every repo on all our machines has it set and some of our internal tooling relies on it being accurate.
We can check what
git thinks the
HEAD of the
origin remote is:
$ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/master
We could also just
cat the file:
$ cat .git/refs/remotes/origin/HEAD ref: refs/remotes/origin/master
git fetch command doesn’t update this
HEAD file. The
git remote command does.
git remote set-head origin -a
git fetch above showed a strange “dangling” error and we can use another command to check on where
git thinks different remote branches point to:
$ git branch -r warning: ignoring broken ref refs/remotes/origin/HEAD origin/main
We need to update the
origin in our local
.git database over to the
We can use
git remote to query the remote repo which should output the correct
$ git remote show origin * remote origin Fetch URL: firstname.lastname@example.org:shareup/base64url-apple.git Push URL: email@example.com:shareup/base64url-apple.git HEAD branch: main Remote branch: main tracked Local branch configured for 'git pull': master merges with remote master
So we ask
git remote to update the local
HEAD ref automatically (that’s what
$ git remote set-head origin -a origin/HEAD set to main
And now our remote tracking information from
git branch is accurate again:
$ git branch -r origin/HEAD -> origin/main origin/main
git branch -m master main
Next we rename the local
master branch to
main. A local branch doesn’t have to be the same name as the remote branch it tracks, but it’s super confusing if it’s not the same. This command doesn’t have any output if it works.
However, renaming a local branch does not change its upstream tracking settings. We can check that with
$ git branch -vv * main c75a5de [origin/master] Add test…
We could also
.git/config to see the same info:
$ cat .git/config [core] repositoryformatversion = 0 filemode = true bare = false logallrefupdates = true ignorecase = true precomposeunicode = true [remote "origin"] url = firstname.lastname@example.org:shareup/base64url-apple.git fetch = +refs/heads/*:refs/remotes/origin/* [branch "main"] remote = origin merge = refs/heads/master
git branch -u origin/main main
Finally we repoint the local branch to track the new remote branch which will update
$ git branch -u origin/main main Branch 'main' set up to track remote branch 'main' from 'origin'.
We can check and it has indeed been updated:
$ git branch -vv * main c75a5de [origin/main] Add test… $ cat .git/config … [branch "main"] remote = origin merge = refs/heads/main
And we can use
git pull to verify that all is working and wired up correctly:
$ git pull Already up to date.
Why write yet another article about how to rename a git branch?
Many articles online
(and GitHub themselves) say to rename the
origin’s branch up on
github.com and then run some commands locally which end up leaving the remote
HEAD ref set incorrectly, leaving an old
master branch, or other similar problems.
Update: I spoke to a friend at GitHub and they’ve updated their instructions to include a
git remote set-head origin -a line. So now if you follow what the GitHub UI shows everything should work out. 😎 🆒
(•_•) ( •_•)>⌐■-■ (⌐■_■)
Maybe you’ve already renamed your branch away from
master and now you are getting some error that seems related. You can check where
HEAD is and update it like this:
# Check where it is now $ git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/master # Update it to point to its main $ git remote set-head origin -a
What even is a symbolic ref?
Symbolic refs are little text files inside the
.git/refs directory. Long ago,
git used symbolic links to keep track of what a branch “points to”, but now is using little text files because it’s compatible with more OS’s.
We can actually print them out to see what they point to. Here are some examples from my local repo:
$ cat .git/refs/remotes/origin/HEAD ref: refs/remotes/origin/main $ cat .git/refs/remotes/origin/main c75a5dee7dc3bce2eba6d5ee116c4c921022d871 $ cat .git/refs/heads/main c75a5dee7dc3bce2eba6d5ee116c4c921022d871 $ cat .git/HEAD ref: refs/heads/main
You can see from this that my
HEAD is pointed to its
main branch. The
origin’s main branch is pointed to commit
c75a5de and so is my local
main branch. And finally you can see my local
HEAD is pointed at my local
main branch. 🤓
git branch renaming 🥳🎊🎉
I hope this can help if you are changing a bunch of default branches (like I am today) 😆