Following on from my earlier blog post on understanding (and misunderstanding) Git, let’s dive deeper into some individual Git topics.
Today’s article is about the branch names
main. It’s the story of a change in the policies, at GitHub and within Git itself, about what the default initial branch name should be. This change, judging by the number of posts and questions about it online, seems to worry a lot of users; but as we shall see, it’s really not a big deal at all.
(You might want to read the earlier post first, before reading this one, so that we’re sharing the same conceptions of what Git is and how it works.)
That syncing feeling
One of Git’s most important features is its ability to synchronize between two copies of a repository living in two different places — typically, a local copy living on your computer, and a remote copy living somewhere off on the Internet. This is good for tasks such as backup (keeping a copy of your work offsite, in case something happens to your computer) and collaboration (because multiple people might be allowed to copy the same remote repository onto their computer and work on it).
A common place to keep the remote copy of a Git repository is GitHub. So you are likely to want a Git repository to live both on your computer and at GitHub. How do you configure that?
Well, it’s easy if you are starting with a situation where the repo you want to use is already stored on GitHub. You tell your local Git to clone that repository onto your local computer. That makes a local copy, and now you’re immediately ready to start working on the local copy.
But suppose there is no GitHub repository to start with. Suppose you want to create both a GitHub repository and a local repository, and pair them and keep them synchronized. That’s the situation that this article is about.
Why is this worth discussing? It’s because, lately, there has been a lot of confusion about how to do it — thanks to a change that GitHub made, starting in October of 2020. That change has to do with the default name of the very first branch in your repository.
What’s in a name?
By default, as a long-standing convention, when you first create a local Git repository by saying
git init on your computer, the new repository has a single branch called
master. Okay, that’s not completely accurate, because when you first create a local Git repository, it appears to have no branches at all. A branch, after all, is just a name for a commit; but a newly created Git repository has no commits.
You don’t actually have a branch until you have some files and you first say
git add and
git commit. At that point, a branch is created for you. But that branch has a name! Where did that name come from? Well, under the hood, a newly created Git repository has its
HEAD configured to point to
refs/heads/master — even though there is no actual
master branch yet, because you haven’t yet made your first commit. As a result, when you do make your first commit, the branch name pointing to it is
In recent versions of Git, you are given the ability to change that behavior. There are actually two ways to change it. When you say
git init to start a repository, you can say:
git init -b somename
to set the default initial branch name for this repository to
In addition, by saying:
git config --global -add init.defaultBranch somename
you can set the default branch name for all repositories that you create with
git init, so that
somename is the default initial branch name.
Indeed, if you have not configured that setting, recent versions of Git will prompt you to do so. When you say simply
git init, you’ll see a message like this one:
Using 'master' as the name for the initial branch. This default branch name is subject to change. To configure the initial branch name to use in all of your new repositories, which will suppress this warning, call: git config --global init.defaultBranch <name> Names commonly chosen instead of 'master' are 'main', 'trunk' and 'development'. The just-created branch can be renamed via this command: git branch -m <name>
Let’s study that message. First, Git is advising you to do what I just said: use
git config to set a default initial branch name. If you do that, the message will no longer appear when you say
Second, Git is telling you that if you don’t like the initial branch name
master that is being used in this new repository, you can change it right now by saying, for example:
git branch -m yoho
This means that when you now do an add-and-commit and the initial branch is created, it will be called
yoho. Observe that you are allowed to make this change even though there are no visible branches yet! Git responds simply by changing the configuration of its
HEAD so that it points to
refs/heads/yoho, even though that branch doesn’t yet exist, any more than
Even if you leave the initial branch name set at
master and you then go ahead and make some commits on
master, you can still change the name of
yoho, using the very same command. In fact, you can change branch names at just about any time, though of course the implications can become complex if this repo is being shared through a remote repository.
In earlier versions of Git, you can’t specify a branch name when you say
git init and there is no
init.defaultBranch configuration setting. However, you can still change the name of an existing branch using
git branch -m. So you can start with the default
master branch and simply change it if you don’t like it.
A remote repository, such as GitHub maintains, is slightly different, in that it is usually a bare repository, meaning that it has no working tree. It consists entirely of information stored inside its .git folder; no branch is ever checked out. Nevertheless, a remote repository still has a default branch name, as signified by the value of its
HEAD. This has two primary uses:
When you clone the remote repository to your local machine, the
HEADbranch is the branch that, by default, will be checked out into your working tree.
Various other aspects of GitHub make assumptions about the default branch being primary. For instance, when you push a branch and create a pull request, GitHub will typically start by assuming that you intend to merge this branch into the default branch.
The main thing
In the warning message from Git that I showed in the previous section, there appeared this sentence:
This default branch name is subject to change.
In other words, the Git folks are signalling that they may eventally make an internal change, such that some other name, not
master, will be the default branch name when a repository is first created. This is presumably in response to the general disfavor into which the word “master” has fallen.
But (and this is important) the Git default branch name has not changed yet. It is still
master. Meanwhile, the GitHub people have decided to jump the gun and start obeying a different convention already. As a result, when you create a Git repository on GitHub, it now has, by default, a single branch called
This is the change that has made people so nervous. And you can readily see why. If you start a repository both on your local computer and at GitHub, and if the local repository has a branch called
master while the GitHub repository has a branch called
main, they are evidently not the same branch. Most people would prefer to avoid that situation; they’d like both the local primary branch and the GitHub primary branch to have the same name —
main, one or the other.
So I’m going to talk about how to make sure that that’s the case. But first, let’s make sure we understand what it means for a local repository and a remote repository to form a pair in the first place.
Local and remote
What is required to form a local Git repository and a remote Git repository into a synchronizable pair? First, there obviously must be both a local Git repository and a remote Git repository. Second, the local Git repository must be told about the remote Git repository. (I went into this in my earlier article, but I’ll just repeat myself here for clarity.)
You could tell the local Git repository about the remote Git repository by using, explicitly, the URL of the remote repository every time you give a command that involves talking to it over the network, such as
push. Typically, however, it is a lot easier just to use a name. The way you give a remote Git repository URL a name in your local repository is with the
git remote add command. By convention — and again, this purely a convention — that name is usually
So, let’s say I have created a remote repository at GitHub, naming it testing. If I click the Code button in the GitHub web page interface for my testing repository, the little popup reveals that the URL for this repository is
firstname.lastname@example.org:mattneub/testing.git. That is the URL that we want to refer to for this remote repository.
Now, I could just say things in my local repository like this:
git fetch email@example.com:mattneub/testing.git
But life will be simpler if I associate that URL with a name, such as
origin. And I do that with
git remote add, like this:
git remote add origin firstname.lastname@example.org:mattneub/testing.git
This defines the name
origin. Now I can say, instead:
git fetch origin
The existence of a named remote does not necessarily associate any actual local branches with any of the branches at the remote repository. That association is a separate step, often performed the first time you push a branch from the local repo to the remote repo. You have probably seen the command expressed this way:
git push -u origin master
That command not only pushes the local
master branch to the remote
master branch; it also associates the two branches. The
-u stands for upstream; you are pushing, and you are also setting the remote
master branch as the upstream of the local
master branch. If you recall the discussion in my earlier article, you’ll know that in fact this means that a remote-tracking branch
origin/master is created and that the local
master is configured to track that remote-tracking branch.
When you don’t already have a local repository, and you obtain one by saying
git clone to make a local repository copied from a remote repository, there is no need to say
git remote add or to set the upstream when you push the initial primary branch; all of that has already been taken care of for you. But if you create a new branch and then want to push it, you will still probably want to say
git push -u origin.
GitHub’s web interface
Once your local Git repository has been told that it has a remote counterpart called
origin (or whatever), you can easily ask your local Git to synchronize with the remote Git. The primary commands for doing this are
git fetch, and
git pull. When you give these commands, you give them locally, to your local Git. You are asking your local Git to make a phone call (as it were) to the remote Git and pass information between the local repository and the remote copy.
But GitHub itself also has an interface that you can see on your own computer — in a web browser! When you go, in your browser, to http://www.github.com/myUserName/myRepo, you are looking directly at the remote repository, and you can give commands to the remote Git, almost as if you had magically flown to wherever GitHub’s computer is and were using it as your local. When you do this, you are completely bypassing your local Git.
So for instance, imagine that we have our local–remote pair, and we want to add a file called myfile.txt to our repository. We can do this from either end, as it were.
Locally, you can create and edit myfile.txt and say:
git add myfile.txt git commit -m 'created myfile'
Now the file myfile.txt exists and is committed on the current branch — locally. But that commit doesn’t exist yet on the remote Git — and therefore, neither does the file myfile.txt. To make that commit and that file exist on the remote Git, you would tell your local to
But you could just as well do the same thing from the other end! In your browser, in GitHub’s web interface, you can click “Add file” and choose “Create new file”. Now you can name and edit this file and commit it, right there in the remote Git. Let’s call it myfile.txt. Now this file exists and is committed on the remote Git — but it doesn’t exist yet on the local Git. To make that commit and that file exist locally, you would tell your local to
In general, talking directly to a remote Git, behind the back of your local Git, is rather an odd thing to do. Still, it can come in handy. And there is at least one task for which it is more or less essential — making a new remote repo at GitHub! There really is no better way to perform this task. Making a remote repo is not a task you can ask your local Git to perform for you. You can’t “push” a local repo to GitHub to create a new remote repo. You need either to create the remote repo and clone it to your local, or create the repo in both places (GitHub and local) and associate the two, as I described earlier.
(I’m not saying there’s no way to create a remote repo at GitHub from the command line. I’m just saying you can’t do it with Git! You could, for example, install the
hub tool and use it to create a remote GitHub repo. But then that tool is simply communicating directly with GitHub behind the scenes, completely parallel to what you would do by way of the GitHub web interface.)
Where’s the beef?
If you create a new repo both locally and at GitHub, the default branch name in the local repo might be
master while the default branch name at GitHub might be
main. This is a potential mismatch. But it’s really not a problem, because you can just change the branch name at either end (or both ends, if you like). Make the names match, and all will be well.
How do you change the branch name locally? You already know that. If the remote default branch name is
main but the local initial branch name is
master, you can just say this to your local Git:
% git branch -m main
How will you know the remote default branch name? Well, you could look at the GitHub web interface and find out. But here’s a cooler way:
% git remote add origin email@example.com:mattneub/testing.git % git remote show origin * remote origin Fetch URL: firstname.lastname@example.org:mattneub/testing.git Push URL: email@example.com:mattneub/testing.git HEAD branch: main Remote branches: main new (next fetch will store in remotes/origin) % git branch -m main
Note the use of
git remote show. This actually goes onto the network and queries the remote repository. Look at this line in the response:
HEAD branch: main
From that line, we learn that the remote’s
HEAD branch is
main. So we rename our own default initial branch to match that.
Now let’s talk about the other end of the partnership — GitHub. The name
main may be GitHub’s choice of default initial branch name, but you are not tied to it. Suppose that you have created a new GitHub repository and it has a
main branch, and you regret that name. No problem; you can change it.
How you do this depends on whether the GitHub repository has any content. You can give a GitHub repository content as you create the repository. When you make a new GitHub repo, GitHub shows you some checkboxes for things you can add at the outset, such as a README file.
If you do not check any of those checkboxes, then when you create this Git repo at GitHub, there is nothing in the repo, so there is no default branch — because there are no branches at all. In that case, just work on your local Git and push its initial branch to GitHub; for example:
git push -u origin master
master is the default branch at GitHub.
But now suppose, instead, that when you created the repository at GitHub, you checked the checkbox to add a README file. In that case, you have actually created the default branch, and its name may be
main. If you don’t like that, you can simply change it in GitHub’s web interface. Click the “1 branch” button above your code (or just go directly to https://github.com/yourName/yourRepo/branches) and click the edit button (with a pencil icon) at the right of the
main branch listing. A dialog pops up where you can change the name of your default branch.
Clash of the titans
So far, as we’ve seen, there isn’t actually any problem. Your local repository and your corresponding GitHub repository may start out life with different default initial branch names; but you can change either one of them (or both) so that the names match.
Now, however, I’m going to discuss a situation where there can be a problem. This is when you have created a GitHub repo with initial content, such as a README, and your local repo also has initial content (because you said
git add and
git commit) — different initial content. In that case, if you change the name of the GitHub default branch or the local branch (or both) so that they match, you can neither push nor pull.
I’ll demonstrate. Let’s suppose you’ve called the branch
master both locally and on GitHub (but it doesn’t matter whether it is
main or anything else, as long as it has the same name both locally and remotely). Let’s suppose both the remote repository and the local repository each have independent content (at least one commit) on that branch. Let’s say you’ve used
git remote add origin to associate the GitHub remote repository with the local repository. Now if you try to push, this happens:
% git push origin master To github.com:mattneub/testing.git ! [rejected] master -> master (non-fast-forward) error: failed to push some refs to 'github.com:mattneub/temptemp.git' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Integrate the remote changes (e.g. hint: 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.
And if you try to pull, this happens:
% git pull origin master From github.com:mattneub/temptemp * branch master -> FETCH_HEAD fatal: refusing to merge unrelated histories
What’s going on? Why is Git being so recalcitrant? It’s because, although these branches may now have the same names, nevertheless, you added something to the branch locally and you added something to the branch at GitHub. Thus, they have completely independent unrelated histories.
This is actually a fairly common quandary — but has nothing to do with the
main name issue! It’s a totally different matter. This sort of problem can arise at any time when you’re working with Git and GitHub. And, once again, it’s quite easy to solve. There are various ways to solve it, actually; but in the special case where we are talking about the initial content of the default branch, my favorite approach is just to do this:
$ git pull --rebase origin master $ git push -u origin master
Well, that wasn’t so bad, was it? But what did I just do?
The first command,
pull, copies the branch down from the remote and joins the local branch onto the end of it. By specifying
--rebase, we enable this to happen even though the branches don’t have a common history, plus we avoid creating an unnecessary merge commit. From a historical point of view, we have lined up the initial GitHub commit followed by the initial local commit to constitute a single history on the
The second command,
push, pushes that branch back up to the remote, associating the remote branch with the local branch as its upstream while making the remote repository’s branch match the local repository’s branch.
On this branch, your local and remote repositories are now in sync! So you can just proceed to work on your local repository as you wish.
The icing on the cake
Here’s a cool fact: the technique that I demonstrated in the previous section can work even if you don’t change the name of one of the branches beforehand! It’s a little more work in that case, but not much.
To experiment, let’s start over. Create a repository at GitHub with a README committed on
main, and also go through the whole dance where you say
git init to create a repository locally, give it a file committed on
master, and use
git remote add to tie the two repositories together. You can now simply do the same pull-with-rebase as before — except that the pulled branch is
main, even though locally we are on
$ git pull --rebase origin main
The result is that we have lined up the initial commit from GitHub followed by the initial local commit, both of them on
master. Now we push, just as before:
$ git push -u origin master
master is in sync in both places. The only problem is that
main still exists at GitHub! We no longer need it — because we have copied its commit onto
master — so let’s get rid of it.
First we have to tell GitHub that
main, should be the primary branch from now on. In the GitHub’s web interface, use the gear icon to go to Settings and then Branches. We are shown the “Default branch”, which is
main. At the far right is a little icon showing two sideways arrows. Click it! In the pop-up window, switch
master and click Update (and laugh uproariously at the warning you get).
Now back in local Git, say
$ git push --delete origin main
master exists both locally and at GitHub.
The icing on the icing
Okay, so I’ve been holding something back all this time. Here it is: There is absolutely no reason why a branch with one name in the local repository should not correspond to a branch with a different name in the remote repository. The names are just names! They are not magic.
So if you’ve created a local repository with initial branch
master and a remote repository on GitHub with default branch
main, you can configure your local
master branch to correspond to the remote
main branch. That way, you don’t have to rename any branches or delete any branches!
Here’s how. First, having configured
git remote add locally, working on
master, do the same
git pull as in the previous example:
$ git pull --rebase origin main
master back up to the remote
main — and tie the two branches together:
$ git push -u origin master:main
And now just go on living life. When you are on
master locally, whether you
push, you’ll be talking to the GitHub remote’s
main branch. So even though the branches have different names, there was never any real problem with that in the first place.