1. Overview

Git is a popular distributed version control system that allows developers to collaborate across different machines. With Git, different developers can make a copy of the repository and work separately with their changes, which are usually isolated in a branch.

When multiple developers work on the same branch, everyone must synchronize their branches frequently to ensure their local copy reflects the latest changes from other copies.

In this tutorial, we’ll learn how to synchronize our local branch with the remote branch in the Git repository.

2. Git Branch Overview

A branch in the Git versioning system is a lightweight pointer that the Git system maintains. This pointer points to a specific commit on the chain of commits in the repository. Consider a repository with several commits:

$ git log --oneline
8f5d8eb (HEAD -> main) (feature) implementing the account restriction function
2bf2caa (bugfix) fix creation issue
a25e624 (feature) implementing the account activation function
192988a (feature) implementing the account creation function
891f9dc Init

The example above uses the git log command to print the commits of the current branch. The output shows that the main branch points to the commit 8f5d8eb.

Internally, Git maintains the branches as files in the .git/refs/heads directory of the local repository. Within the files, we can find the commit hash the branch points to. For example, we can use the cat command to see the commit hash the main branch is pointing to:

$ cat .git/refs/heads/main
8f5d8ebe365abb8d860b0e97d4e0f8d0fb9cec03

In Git, a branch can be a local branch or a remote-tracking branch. Additionally, we can further categorize a local branch into a tracking branch and a non-tracking branch.

Let’s dive into these concepts.

2.1. Local Non-tracking Branch

A local non-tracking branch is a local branch that does not tie to any of the remote branches. Specifically, the branch only exists locally and does not track any remote branch.

We can create a new local non-tracking branch using the git checkout -b command, followed by the branch name:

$ git checkout -b feature/authentication-function
Switched to a new branch 'feature/authentication-function'
$ git log --oneline
8f5d8eb (HEAD -> feature/authentication-function, main) (feature) implementing the account restriction function
2bf2caa (bugfix) fix creation issue
...

In the example above, we use the checkout command to create the feature/authentication-function branch.

2.2. Remote-Tracking Branch

A remote-tracking branch is a special branch object in Git. Concretely, it’s a local reference that tracks the state of the branches of the remote repository. Notably, the remote-tracking branches cannot be updated by the user. Instead, Git updates these branches when we receive new changes from the remote branch through the git fetch command.

The sole purpose of a remote-tracking branch is to maintain a local reference to the remote branch. They act as a cache for the states of the remote branches, allowing commands to reference them without a trip to the remote repository.

The remote-tracking branch name follows the naming convention of /. For example, in a repository with a remote origin, we’ll have local remote-tracking branches prefixed with the origin:

$ git branch -r
  origin/HEAD -> origin/main
  origin/feature/authentication-function
  origin/main

The example above uses the git branch -r command to list all the remote-tracking branches.

To work on a remote branch, we’ll need to create a local branch that tracks the remote branch.

2.3. Tracking Branch

A tracking branch in Git refers to local branches that directly relate to a remote branch. To create a tracking branch, we can use the git checkout -b command.

Importantly, to make the local branch track a remote branch, the git checkout -b command must be followed by the local branch name and the name of the remote tracking branch:

$ git checkout -b hotfix/patch-1 origin/hotfix/patch-1
Switched to a new branch 'hotfix/patch-1'
branch 'hotfix/patch-1' set up to track 'origin/hotfix/patch-1'.

From the command output, we can see that the local hotfix/patch-1 branch is tracking the branch with the same name at the origin remote.

With this linkage, Git can detect and report any divergence between our local branch and the remote branch it’s tracking. Let’s say we run the git fetch command to update our remote-tracking branch and find out that the remote branch has advanced:

$ git fetch
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 2 (delta 1), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (2/2), 225 bytes | 45.00 KiB/s, done.
From /home/user/Projects/baeldung/git-sync-branches/account-service
   5a77e86..fe25a6d  hotfix/patch-1 -> origin/hotfix/patch-1

Then, we can run the git status command to see that our tracking branch is lagging behind the remote branch:

$ git status
On branch hotfix/patch-1
Your branch is behind 'origin/hotfix/patch-1' by 1 commit, and can be fast-forwarded.
  (use "git pull" to update your local branch)

nothing to commit, working tree clean

With a solid grasp of the concept of remote and local branches, let’s check out the different commands we’ll use for synchronizing between the branches.

3. Synchronizing Local and Remote Branches

To synchronize the local and associated remote branch, we’ll either need to push new commits locally to the remote or bring the changes from the remote to the local.

To bring our local branch up to date with the associated remote branch, we can run the git pull command:

$ git pull

The git pull command combines the git fetch and git merge commands to update the local tracking branch to the remote branch. It first runs the git fetch to get the new commits and the remote branches’ state from the remote repository. Then, the git merge merges the remote branch with the local tracking branch.

To introduce local new commits to the remote branch, we can use the git push command:

$ git push
To /home/user/Projects/baeldung/git-sync-branches/account-service
 ! [rejected]        hotfix/patch-1 -> hotfix/patch-1 (fetch first)
error: failed to push some refs to '/home/user/Projects/baeldung/git-sync-branches/account-service'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

The git push command copies all the new local commits on that branch and applies them sequentially to the remote branch it’s tracking. Importantly, the command doesn’t perform a merge and will fail if the new commits cannot be applied without merging on the remote.

In the example above, the git push command fails because the remote branch has newer changes that we’ll need to incorporate into our local first. Therefore, it’s a great practice to run git pull first, then git push:

$ git pull
remote: Enumerating objects: 3, done.
...
   a2b4ec8..c2fd749  hotfix/patch-1 -> origin/hotfix/patch-1
Merge made by the 'ort' strategy.
 patch-2 | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 patch-2
$ git push
Enumerating objects: 12, done.
Counting objects: 100% (12/12), done
....
   c2fd749..30f295f  hotfix/patch-1 -> hotfix/patch-1

4. Conclusion

In this tutorial, we learned that the Git branch object is a pointer to a specific commit. Then, we learned the difference between local branch, tracking branch, and remote-tracking branch. Finally, we looked at the various synchronization scenarios between the local and remote branches that use the git push and git pull commands.