Of Git and GitHub, Master and Main

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 master and 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 master.

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 somename.

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 git init.

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 master does.

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 master to 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 HEAD branch 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 main.

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 — master or 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 fetch or 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 origin.

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 git@github.com: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 git@github.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 git@github.com: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 push, 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 git push.

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 git pull.

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 git@github.com:mattneub/testing.git
% git remote show origin
* remote origin
  Fetch URL: git@github.com:mattneub/testing.git
  Push URL: git@github.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

Now 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 master or 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 master vs. 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?

  1. 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 master branch.

  2. 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 master:

$ 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

Fine, now 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 master, not 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 main to master and click Update (and laugh uproariously at the warning you get).

Now back in local Git, say

$ git push --delete origin main

Now only 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 origin with git remote add locally, working on master, do the same git pull as in the previous example:

$ git pull --rebase origin main

Now push 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 pull or 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.

You Might Also Like…

Understanding Git Merge

Carrying on from my earlier article about some ways in which Git is commonly misunderstood — and how I think one should understand Git — I’d like to dive a bit deeper into one of the most important things Git knows how to do: merging. If Git is often misunderstood, merging is one of the …

Understanding Git Merge Read More »