What are Branches?

Until now we have been dealing solely with the master branch as we saw in some commands. But what is a branch? Git is all about commits. At any point in time, we are always in some branch.

As you saw in Git history using git log , a Git history is a series of commits linked together forming a chain. A branch is nothing but that chain with a name. When we add a new commit, it gets pushed to the top of that chain. The top commit is now HEAD.

HEAD is just a pointer to the last commit in a currently checked out branch (the current branch we are in). Hence whenever I say HEAD of master branch, it doesn’t mean, master branch doesn’t have different HEAD than other branch. It means HEAD when we are in master branch. So bear with me on this one.

But a branch does not have to remember all commits. It has to remember only last commit and then that commit is linked with another commit and so on. You can visualize a branch as tuple of a commit and branch name.

branch = (commit, branch name)

When we initialize a repository, master is the default branch without any commits. Once we make a commit, that commit becomes the HEAD. Once we start adding more commits, HEAD will point to whatever is at top of the chain and branch has to only remember that commit. Hence a branch is nothing but a tuple of the branch name and HEAD commit.

When we create a new branch, we are creating a new tuple with a branch name and a commit. The commit for the new branch is taken from the last commit of another branch. If we are inside master branch and we instructed Git to create new branch, Git will pick up last from master branch. Once we switch the branch, HEAD will point to the last commit of the current branch.

But why do we need branches? Well, it’s a standard development practice in small to large organizations that every developer should work on his/her own branch. Once he/she is done with development, he/she can test the code before it gets merged in the master branch which could be the production branch. That way, the accidental deployment of the buggy code can be prevented.

Alright. Let’s create a branch with the name dev . To create a branch, first, we need to make sure we are inside the correct branch with the begin with. Right now, we are inside the master branch and you can verify that by looking at your terminal or by checking how many branches are present in the repository. The one with an asterisk is the branch you are currently in.

git branch

(git branch)

To create a branch, you need to use the following command.

git branch dev

(git branch dev)

This will create dev branch but we are still under master branch. To enter inside dev branch, we need to use checkout the branch using the command below.

git checkout dev

(git checkout dev)

The above two steps can be carried out at once using git checkout -b dev command which will create and checkout branch at the same time. However if a remote branch with the name dev already exists, we can avoid git branch command or -b flag. Git will create and checkout the dev branch from the remote dev branch and track this remote branch.

As you can see, now we are under dev branch. Let’s see the branch history.

(git log)

As you can see from the above screenshot and compare with the history of master branch, HEAD commit is the same. Hence the files state in both branches are the same. But when we will create a new commit, it will be added to the top becoming HEAD of the dev branch but the HEAD of the master branch won’t be changed. At that point, their files state will be different.

I have changed the didvide.js file and made a commit from it.

// divide.js // return division of two numbers

function divide(a, b) {

return a / b;

};

Let’s create a commit using the following command.

git add -A

git commit -m "divide function available"

You can use git commit -am “divide function available” which will add and commit all the modified files, but not newly created files.

(make new commit)

Now, if we check the history of dev branch, we will see a new commit at the top which will be the HEAD.

(git log)

You might want to push that branch to the remote repository. This could be necessary but if your company is running some sort of automated tests using continuous integration, then pushing a branch might be a good idea.

To check all local and remote branches, use git branch -a . So far there is only one remote branch.

(git branch -a)

You can use git fetch at any time to update your remote-tracking branches. Let’s create a remote branch from GitHub.

(GitHub Remote Repository)

Now, let’s use git fetch and see which remote tracking branches are using git branch -a command.

(git branch -a)

Do you remember how we set the upstream for the local master branch using git push -u origin master command? We need to do the same for the dev branch. If this branch doesn’t exist on the remote repository, it will be created.

If a remote branch already exists with a different name than you want to track with the current branch, then use command git branch --set-upstream-to origin/dev_uday instead and then you just have to use git push .

git push -u origin dev

The above command will create dev branch on remote repository and our local dev branch will track it. Let’s see the output of git branch -a .

(git branch -a)

You can also see other branches on GitHub, via the branches tab.

(GitHub Remote Repository)

Let’s say that continuous integration test on remote dev branch ran well and you (or admin) now have to merge changes made in your dev branch to the master branch. Merging happens between two branches, technically, it is careful mixing of commits of two branches.

Since, we need all changes made in the dev branch to sync with the master branch, we have to checkout master branch:

git checkout master

When we checkout out the master branch, we are referencing a different Git history because our HEAD is different. That means the state of the files associated with this HEAD would be different. Hence Git changes the content of the files in the repository according to that state. You can visualize by opening divide.js file in editor when you are in dev branch and monitor that file when you checkout master branch. You will see content of that file changes as we move from one branch to another branch. You could also see some files appear and disappear.

Since we are now in the master branch, we must pull code from the remote repository before doing anything, always do this. This way, we don’t miss out on any development happened on master branch (done by other developers) whilst the development of dev branch.

git pull

Now, we have to merge the dev branch into the master branch. To check if any branches ever merged with current branch which is master , you can use the command below.

git branch --merged

(git branch --merged)

From the above output, it is clear that no other branches were ever merged in master branch. Since we are already under the master branch, the following command will merge the dev branch with the current branch.

git merge dev

(git merge dev)

The above command also shows files that were changed. You can also verify the merger by executing git branch --merged command. If we see the git history of master branch now, we can see any commits made in the dev branch appears in master branch.

(git log)

Now we just have to sync the local master branch with the remote one. This is done using the same old git push command.

(git log)

If we are done with dev branch and we don’t need it anymore, then we can just delete it using the command below. This will delete the local dev branch only.

git branch -d dev

(git branch -d dev)

You can delete multiple branches in one command using git branch -d branch1 branch2 ... .

To delete the remote dev branch as well, you need to use the command below.

git push --delete origin dev

(git push --delete origin dev)

Fixing common mistakes

We have seen so far that if you are working with a team of people, then you should not touch the production branch which in our case is master . But what if you accidentally forgot to switch branch and made commits inside the master branch? You can’t just remove your commits using git reset and redo the work. That would be painful. In that case, we could use couple of techniques including cherry-picking.

Let’s first create a commit inside the master branch. I am going to add some comments inside add.js like below.

// add.js // return summation of two numbers

function add(a, b) {

return a + b;

};

We are going to make a commit from only this modified file, hence I will use the shorter version of the commit command.

git commit -am "add function comment added"

(git log)

Our Git history shows that commit. But, suddenly we remembered that we were doing commits in the wrong branch. We can’t push this commit(s) to the remote master branch. We need to get rid of commit 96452a and return master branch to the state it was before. But we also don’t want to delete this commit because it contains our work.

What we were supposed to do is create a new branch dev , make changes there and publish that changes there. Later, if changes are approved, merge in master branch. So, let’s do that. Let’s make dev branch first and execute checkout. I am going to use a shorter version of the checkout command.

git checkout -b dev

(git branch)

Now we are inside the dev branch. If we see history of git branch, it should have commit 96452a from master branch.

(git log)

Great. Now we have to go back into the master branch and set HEAD to the commit 1b2ed7 which is the second commit in history.

git checkout master

git reset --hard 1b2ed7

git clean -f -d

We are using git clean because we also don’t want any untracked files or folders to be in the working area. The git history of the master branch now will look like

(git log)

Now, we just have to go back into dev branch, push commits to remote dev branch and wait for the approval. Let’s say that we got approval the changes are ok. It’s time to merge dev branch to the master branch.

git pull

git merge dev

(git merge dev && git log)

And we got our commit back from dev branch. Now we can push this commit using git push .

Let’s think of another situation. What if we made commit(s) in the master branch by accident and we also have the dev branch present? We could create another branch besides dev but let’s assume that we must work in dev branch. Then somehow, we have to bring the commit from master branch to dev branch. Let’s see how we can do it. Let’s first create a commit inside the master branch.

I am going to modify modulus.js file and add some comments.

// modulus.js // return remainder of two numbers

function modulus(a, b) {

return a % b;

};

Let’s create a commit and see the git history.

git commit -am "modulus function comment added"

git log

(git log)

Since we don’t want commit 07762b in the master branch and we want to move this to dev branch, let’s checkout the dev branch.

git checkout dev

(git log)

Git history of that branch doesn’t have the 07762b commit which should be obvious by now because conducted a checkout of an already existing branch. Now, we have to bring this commit from the master branch. As we discussed, commits do not belong to any branch. They are unique and branches only reference them. Hence, we can just instruct Git to bring the 07762b commit without telling from which branch or branches reference it. This is done using cherry-pick .

git cherry-pick 07762b

(git log)

Now that commit has been added to dev branch as we can see from commit message. But the strange thing is, its hash is different. This is because instead of pointing HEAD to the last commit of the master branch, Git copies the commit and points to the HEAD of dev branch. That means we have to go back to the master branch and reset the HEAD to earlier commit.

But let’s make a mistake here again. Instead of resetting the head of the master branch to 96452a4 (second commit in history), we will reset it to 1b2ed7b (third commit in history).

git checkout master

git reset --hard 1b2ed7b

git clean -f -d

(git log)

When we do git status , it should show that our working directory is clean and we haven’t done any changes in master branch since it was synced last time with remote master branch. But instead, we get this message.

(git status)

It shows we are 1 commit behind origin/master which is the remote master branch. How could this be? This could be because we did something wrong in the reset command. Oh, yes. We should have reset the branch HEAD to 96452a4 . What was the earlier commit hash (in case we forgot) because it doesn’t appear in Git history. For that, use git reflog command.

(git reflog)

git reflog prints a complete list of previous operations. The latest operation will be at top. It will also show the HEAD on the left when that operation was performed.

Check for operation before cherry pick which would be moving from master branch to dev branch, we can see HEAD of master branch to be 96452a4 . The thing about Git is, whenever a branch HEAD is moved to an earlier commit, the lost commits are not actually deleted. They are still in the repository and they will be garbage collection in few days if no other branch references them. Luckily we spotted the mistake in time and we can now reset the head to that commit.

git reset --hard 96452a4

(git reset 96452a4)

You can also make use of HEAD{index} in git reflog. You can use the command git reset --hard 'HEAD{5}' which is index before I did git reset . It would be HEAD{0} in your case most of the time, I just spotted this mistake after doing some operation that’s why my index is not 0 .

(git log)

(git status)

Now, our branch is clean and updated with the master branch of remote repository. It’s time to merge the dev branch.

git pull

git merge dev

(git status && git log)

Now we are ready to update our remote master branch using git push .

What if I made a commit in the wrong branch and pushed it? Well, that would be wrong and you would not want to remove commits which are published. This is because you will end up rewriting the history of remote branch when you next time push the branch, but other people have the commit that you deleted. This can cause lot of problems.

Instead, what we want is to create a new commit that will undo the work we have done. Let’s create a simple commit in master branch.

// subtract.js // return subtraction of two numbers

function subtract(a, b, bMINUSa) {

if (bMINUSa === true) {

return b - a;

}

else {

return a - b;

}

};

We made a change to subtract.js file, we are going to create a commit from it.

git commit -am "extra feature in subtract function"

git push

(git log)

Now, our commit is on the remote branch as well. Then we realized our mistake that we were not supposed to make that commit. Since our commit is present on the remote branch, we can’t simply remove it from our local branch and publish it again, because, in between, other developers might have synced the branch or cloned the repo.

Hence, we have to revert the changes. This is done using git revert which creates a new commit by reverting all the changes in a commit. Since, we want to revert all change in commit 7d741e , we need to use the command below.

git revert 7d741e

It will open up vim editor with new commit information which we would like to change.

(git revert 7d741e)

You can also use git revert 7d741e --no-edit instead which would skip the editor dialog.

After running this command and doing modifications to revert commit message, your git history will look like below.

(git log)

We can see that revert created a new commit and deleted changes in the subtract.js file which you can verify by looking content of the file. You can also verify this by using git diff between two commits.

git diff 01f8a6a 4c045d9

Which will output nothing because there is no difference between reverted commit and the commit before changes were made.

Since we did undo the changes which were not supposed to be on the master branch, we should push this revert commit to the remote master branch. This way other remote master branch do not have faulty updates and other people’s history will remain consistent with the remote repository.

Detached HEAD state

You may come across a situation when your git shows a detached head state message in the console. This happens when you are not in a branch. As we discussed, a branch is nothing but a registered pointer to a commit in the repository. When we are in a detached state, the state of the repository is pointing to a commit in history but that pointer is not saved. You can test it with git checkout command.

git checkout hash

Here, hash is a commit hash. This commit will create a branch without a name. Let’s go back to our history in the master branch and pick a commit.

(git log)

Let’s create a unnamed branch from commit 7d741e using the command below.

git checkout 7d741e

(git checkout 7d741e)

Git prints lots of working instructions to recover from this state. It shows that we are in detached HEAD state.

(git branch)

We can see from git status , that we now have a new branch (HEAD detached at 7d741e6 and we are currently in that branch.

Being in a detached state is not a piece of good news, but we can fix it. You can do pretty much anything in this branch like being in a named branch but once you are done with your changes, you should not checkout another branch. Because if you did that then Git won’t remember detached HEAD branch.

(git checkout master && git branch)

Hence, you need to new branch from detached HEAD branch in order to save the commits. Creating a branch from detached HEAD branch is a normal procedure.

git checkout -b dev-2

(git checkout -b dev-2)

Since we applied the checkout command to the new branch, our detached HEAD branch will be lost which is fine because we got the changes from it in the other branch we just created.

You could create a detached HEAD branch if you want a new branch that has HEAD at a commit that you specifically chose. But for that, Git gives simple command git checkout -b new_branch hash which is an extended version of git checkout command. So in the previous example, we could have used the command below instead.