I'm available to deliver your SaaS MVP in a few weeks, full-stack with the new Next.js App Router. Accelerate Your MVP Launch Now.

Remote and tracking branches with Git

Do you know what are remote and tracking branches? Tracking ones can make our lives easier, get to know why and how.

Flavio Silva
Flavio SilvaJuly 20, 2017
Remote and tracking branches with Git
Image by Freepik

If you're new to Git or want to know a little more about its basics and its distributed model, do not hesitate to read my Introduction to Git.

If you're not very familiar with Git branching, or you'd like to refresh your understanding about it, please head to Branching and merging with Git, then come back and move on.

In this article you'll learn what are remote and tracking branches, how they work and how to use them.

Do not type the $ sign you see in the command examples in this article. That's just an indicator that you should run the command that follows it in your command line tool.

What are remote branches?

"Remote references are references to the state of branches on your remote repositories. They're local branches that you can't move; they're moved automatically whenever you do any network communication." (Chacon 50)

So a remote branch is a local branch (created in your local repo) that points to and contains a copy of a branch in some remote repo. It's also ready-only, so you cannot change it, it's supposed to be updated when we run a $ git fetch. A bit confusing? I know, it really is at a first glance.

Starting a new local repo

For the sake of learning, we'll create a very simple Git project/repo and play with it to better understand branching and remote branches. So now please create a new directory, add a couple files in it, and run $ git init. Now, if you run $ git status you should see your files listed as Untracked files. So let's run $ git add ., and then $ git commit -m "adding some files.". Nice, now we have a local repo with some files committed to it.

Starting a new remote repo

Now, take the time to set up a remote repo. You can use GitHub, Bitbucket, or any other service you like.

Adding a remote reference to your local repo

After you have setup your remote repo, let's add a reference to it in your local repo. To do that, run a command like:


$ git remote add origin [your-remote-url-goes-here]

Note that we're giving that remote an alias of origin, but it could be anything we wanted. If you run $ git remote now you should see something like:


origin

If you want more info about your remote repo you can append -v option to that command:


$ git remote -v

In that case you should see something like:


origin [your-remote-repo-url] (fetch)
origin [your-remote-repo-url] (push)

Note that Git lists two references to it, one for reading from your remote repo (fetch), and another for writing to your remote repo (push).

Pushing to a remote repo

Now we can push to our remote repo, so let's do just that:


$ git push origin master

You should see something like:


Counting objects...
...
Writing objects: 100%...
...
* [new branch] master -> master

That means the operation was successfully completed. Git should have created a master branch in your remote repo, and created a reference to it in your local repo. That reference is our remote branch (our remote master branch in this case), created in our local repo. By default, when we push to a remote repo, and there's no remote branch setup for that local branch we're pushing, Git creates one for us automatically, and names it as remotes/remote-name/branch-name, so in this case remotes/origin/master.

Checking your remote branches

If you run $ git branch -r now (-r option means remote), you should see something like:


remotes/origin/master

You can also run $ git branch -a to see all branches, local and remote. In that case, you should see something like:


* master
remotes/origin/master

Your remotes/origin/master remote branch is a copy of your remote repo's master branch, a branch living in your remote repo. At this time they are the same, but when you work on a project with other members, they will also update the project's remote repo, so your remotes/origin/master remote branch will become outdated at some point. Just keep that in mind for now.

Switching to remote branches

You can really switch to remote branches if you want. Let's just try that by running:


$ git checkout origin/master

You should see some output, including something like Note: checking out 'origin/master'. Note that you can omit remotes/ prefix when referring to remote branches.

What's coolest is that it's a local operation, because you have a copy of the contents of your remote repo's master branch exactly as it is. Nice, isn't it?

Cool, now I feel you have a better understanding about what are remote branches. Next, let's see how to update them when someone else pushes code to a remote repo we're working on.

Updating our remote branches

We know that to send our local changes to a remote repository we run a $ git push command. But it's a good practice to always run a $ git fetch command before $ git push, as we've seen here. The only exception is when we're pushing a new branch to our remote repo, effectively creating that new branch in our remote repo. In that case, since that branch didn't exist in that remote repo yet, there's nothing to fetch from.

A $ git fetch command synchronizes our local repo with a remote repo:


$ git fetch [remote-name]

By synchronize we understand a copy of all branches in the remote repo to our local repo as remote branches. If a remote branch already exists in our local repo, it is updated.

But a $ git fetch command doens't touch our local branches (master, etc), only our remote ones (origin/master, etc). So our master branch keeps outdated if we get new changes from our remote repo when we run $ git fetch. We need to $ git merge those changes into our local master branch after running $ git fetch. We'll do that in a moment, first let's see why we need to run $ git fetch before $ git push one more time.

Why do we need to run fetch before push?

If someone else has pushed content to a remote repo, you need to $ git fetch those changes first, and $ git merge them into your local branch. Only then you can $ git push your changes. Git doesn't allow us to push our changes to a remote repo if our local branch is outdated in relation to our target branch. In other words, if someone else has updated our remote repo's master branch, we first need to merge those changes into our local master branch, and only then push our changes. That's the same for any branch, not only master. Note that you don't need to worry about branches that other people are pushing to a remote repo if you're not working on them.

Faking a team member

Since we're not working on a real project at the moment, let's fake a team member working on our project, pushing changes. To do that, you can create another folder in your filesystem (outside your current repo, so not a subfolder of it), and clone your remote repo into it by running:


$ git clone [your-remote-url-goes-here]

Now, point to that new repo in your command line tool, make some small changes to any file, then commit and push it:


$ git add .
$ git commit -m "some change from cloned repo."
$ git push origin master

You should see some output, including Writing objects: 100%.

Integrating team members' changes

Now, let's get our command line tool back to point to the first repo we created, the one we started by using $ git init.

At this point, you should note that this first repo is now outdated, as our remote repo was just updated by another team member. But let's pretend we don't know about that, we're working on our own stuff. So let's make some other change, to another file (or to the same file but on a different line just so we avoid any conflicts), add it, commit it, and try to push it:


$ git add .
$ git commit -m "some change from init repo."
$ git push origin master

You should see an error message like:


To [your-remote-url]
! [rejected] master -> master (fetch first)
error: failed to push some refs to '[your-remote-url]'
hint: Updates were rejected because the remote contains work that you do not have locally.

Yup, that's expected. Git makes it pretty clear stating that Updates were rejected because the remote contains work that you do not have locally.

So let's fetch those changes by running $ git fetch origin. You should see some output, including something like Unpacking objects: 100%. Nice, now our (local) remote branch origin/master is updated with the contents of our remote repo's master branch. Let's merge those changes into our local master branch by running:


$ git merge origin/master

That should launch your system's default text editor with Git's default merge commit message, something like Merge remote-tracking branch 'origin/master'. Just save and close it. If for some reason you get an error message like:


error: There was a problem with the editor 'vi'.
Not committing merge; use 'git commit' to complete the merge.

Don't worry, you just have to commit it manually. In that case just run:


$ git commit -m "Merge remote-tracking branch 'origin/master'"

Nice! Your local master branch is now up to date with origin/master, and ready to be pushed, so let's do that:


$ git push origin master

You should see a successful message, including Writing objects: 100%.

And that's it for remote branches. That's a pretty common flow that you're probably going through when working on a remote repo alongside other people.

Remember: you only have to worry about merging remote changes if there were changes pushed to the remote repo by another person, so changes that you do not have in your local repo, and if they changed branches that you're working on.

Now, let's take a look at a closely related topic: tracking branches.

What are tracking branches?

"Tracking branches are local branches that have a direct relationship to a remote branch. If you're on a tracking branch and run git push, Git automatically knows which server and branch to push to." (Chacon 57)

A tracking branch is just your regular local branch, but with some extra tracking information. It tells your local branches to track remote branches.

Trying to push, the short version

Let's continue playing with our local repo. Make sure that you point your command line tool to your local repo that you started by running $ git init, not the one you cloned (this is important). Now, change some file, add and commit it. Now, just try to run $ git push. You should see an error message like:


fatal: The current branch master has no upstream branch.
To push the current branch and set the remote as upstream, use
git push --set-upstream origin master

That means Git doesn't know where to push your local branch to. It doesn't know which remote repo to use, and how it should name it in the target repo (you can push your local master branch using a different name, Git doesn't assumes you want to use the same name).

Pushing, the long version

So far, we've been running $ git push like so:


$ git push origin master

That tells Git to push our current branch to our origin remote and name it master. If such branch exists in our remote repo, it's updated, otherwise it's created.

But we're looking for a short version of that. And that's why we need tracking branches.

Creating tracking branches

Let's make our local master branch track our remote master branch. Git told us how to do that in that error message:


$ git push --set-upstream origin master

But there's a shorthand for that command:


$ git push -u origin master

You can do that at any time. If you don't have local changes to push, Git returns a message like Everything up-to-date as usual, but in this case, since we're providing a -u parameter it also returns a message like Branch master set up to track remote branch master from origin. Nice, looks like it worked! Do you think so?

Pushing, the short version

Let's make sure that worked. Let's make one more change to any file, add and commit it. Now, let's try to push it the short version, without providing remote-name and branch-name:


$ git push

You should see a regular success message. Awesome! Now you have your master branch tracking your remote master branch, and can easily pull and push to it without having to provide remote-name and branch-name every time. You can do the same for pull, running just $ git pull. Nice, huh?

Creating new tracking branches

When you create new branches, you can append -u when you first push it to your remote repo, like:


$ git push -u your-remote-name your-branch-name

There's no problem if you forget to do that the first time you push your new branch to your remote repo, you can do that at any time.

More advantages of using tracking branches

Besides being able to run $ git push and $ git pull in a short form, another advantage of having tracking branches is that they show you when you are ahead or behind your remote branch when you run $ git status, meaning you need to push your changes, or pull someone else's changes. The following is a typical output of $ git status from an unchaged local master branch that is not a tracking one:


On branch master
nothing to commit, working tree clean

But if that branch was a tracking one, you'd see something like:


On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working tree clean

Did you notice that extra line? It says your branch is up-to-date with your remote branch. But there's a gotcha here: if someone else updates your remote repo, you'll still get that message, because Git is looking at your local remote branch (remotes/origin/master), not going to your remote repo to look at that branch there. In that case, if you run $ git fetch your local remote branch will be updated, then if you run $ git status again, you should see something like:


On branch master
Your branch is behind 'origin/master' by 2 commits, and can be fast-forwarded.
(use "git pull" to update your local branch)
nothing to commit, working tree clean

Git tells you that you're behind origin/master, and that you can run $ git pull to update your local branch.

Now, when you make local changes, commit them, and run $ git status before pushing your changes, you should see a message like:


On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
nothing to commit, working tree clean

Git tells you that you're ahead of origin/master, and that you can run $ git push to update your remote branch with your local changes.

A tracking branch for free when cloning

One thing to note is that when you clone a repo, Git creates a remote branch on your new local repo for each branch that exists in that remote repo. It also creates your local default master branch, as it does when we start a repo by running $ git init. But since it also adds a origin remote for you (a reference to a remote repo), pointing to that remote repo you cloned, it goes even further and sets up your just created local master branch to track your remote master branch, which points to your origin remote. Nice, isn't it? And how nice is getting to know some of the things Git does behind the scenes for us?

When you need to work on another person's branch

Let's picture a scenario where you just cloned a repo that contains some branches created by some team members. When you clone a repo, Git creates remote branches for you automatically, remember? But it doesn't create local branches for you except master. So, let's consider you have to do some work on one of those remote branches, say new-feature-a, started by another person.

Let's pretend you see the following when you run $ git branch -a in that hypothetical repo:


* master
remotes/origin/master
remotes/origin/new-feature-a
remotes/origin/new-feature-b

Ok, so you know there is a new-feature-a branch created in your remote repo by someone else, and you need to work on it, but you don't have such local branch yet. In that case, what you want is kind of a clone of that remote branch as a local branch, and why not, having it as a tracking branch of that remote one. That looks like a few things to do, but you can do all of them with a single command:


$ git checkout -b [your-local-branch-name] [remote-name/branch-name]

Note that you can name your local branch anything you want, but most of the times you'll want to match your remote branch's name. In our hypothetical case, we do so by running:


$ git checkout -b new-feature-a origin/new-feature-a

You should see an output like:


Branch new-feature-a set up to track remote branch new-feature-a from origin.
Switched to a new branch 'new-feature-a'

That command creates a local new-feature-a branch with the same content of origin/new-feature-a, and makes it a tracking branch of origin/new-feature-a.

Conclusion

That's it. You can now say that you know what remote and tracking branches are! Cool, right? But don't worry if things are still a little confusing. As a next step you should practice it a lot. Play with commands, create repositories, branches, merges, conflicts (and solve them!), and hopefully everything will look more natural in a few days.

How to install Git with Homebrew on macOS
Branching and merging with Git
Basic Git commands explained
Git workflow
Tagging with Git
Introduction to Git

Git
Pro Git Book

Bibliography

Chacon, Scott. Pro Git. Apress, 2009.

Remote and tracking branches with Git by Flavio Silva is licensed under a Creative Commons Attribution 4.0 International License.

Leave a comment using your GitHub account

© 2024 Flavio Silva