Previous chapter
Git Version ControlBranches
Next chapter

Introduction

If you don’t use version control, a common workflow is to create different subdirectories to hold different versions of your project in different states, for example development and final. Of course, then you always end up with final-updated and final-updated-revised as well. The problem with this is that it becomes difficult to work out if you have the right version of each file in the right subdirectory, and you risk losing work.

One of the reasons Git is popular is its support for creating branches, which allows you to have multiple versions of your work, and lets you track each version systematically.

Each branch is like a parallel universe: changes you make in one branch do not affect other branches (until you merge them back together).

In the diagram below, each box is a commit and the arrows point to the next (“child”) commit. How many merges have taken place?

{width=50%}

How can I see what branches my repository has?

By default, every Git repository has a branch called master. To list all of the branches in a repository, you can run the command git branch. The branch you are currently in will be shown with a * beside its name.

Switching and Creating

Switching Branches

Switching branches is done using the git checkout command. To switch to branch next you can type

git checkout next

Switching back to another branch, e.g. master is then accomplished using

git checkout master

Please note, that you should not have any uncommitted changes in your branch before switching.

Creating Branches

Using the command git branch <branchname> you can create a new Git branch in your repository. The branch will be based on the current HEAD in your repository. Its contents are thus identical to the contents of the original branch. Once you start making changes, they only affect the new branch. To create a new branch issue-123 in your current repository you can type the command

git branch issue-123

If you immediately want to switch to the branch after creation you can use the command

git checkout -b issue-123

Exercise

The output of git log --format=oneline on branch next of a specific repository is as follows:

f54aca90848cfe7c0bb536d6d9f86cb011f5369d (HEAD) Deactivate openmp conf
ac8dacfb24520bbd74344ac8417232fbc113dff8 Changing package version
6852ad0c2f8b91c2abb92108e62929c78c3827ca Returning to the original name
a1df38ffe4061d76d2f712fe098c918a498add7a Initial commit

The output git log --format=oneline on branch master looks as follows:

a1df38ffe4061d76d2f712fe098c918a498add7a (HEAD) Initial commit

Deleting Branches

After some time you might want to clean up your repository and remove unnecessary Git branches from your repository.

Delete a local branch

To delete a local branch from your repository you can use either of these two commands:

git branch -d branch_name
git branch -D branch_name

The -d option stands for --delete, which would delete the local branch, only if you have already pushed and merged it with your remote branches.

The -D option stands for --delete --force, which deletes the branch regardless of its push and merge status, so be careful using this one!

Delete a remote GIT branch

To delete a remote branch you can use the following command:

git push <remote_name> --delete <branch_name>

Alternatively, there is this other following option too, which might be just a bit hard to remember:

$ git push <remote_name> :<branch_name>

These two options can also be used if you want to delete a tag.

Cherry Picking

In some situations it might be necessary to only apply specific commits from a different branch. This is where git cherry-pick comes into play: It applies a commit identified by its hash on the current head. This is in contrast with other ways such as git merge and git rebase which normally apply many commits onto another branch.

Before using git cherry-pick make sure you are on the branch you want to apply the commit to.

git checkout master

Execute the following:

git cherry-pick <commit-hash>

Exercise

The output of git log --format=oneline on branch next of a specific repository is as follows:

f54aca90848cfe7c0bb536d6d9f86cb011f5369d Deactivate openmp conf
ac8dacfb24520bbd74344ac8417232fbc113dff8 Changing package version
6852ad0c2f8b91c2abb92108e62929c78c3827ca Returning to the original name
a1df38ffe4061d76d2f712fe098c918a498add7a Initial commit

The output git log --format=oneline on branch master looks as follows:

a1df38ffe4061d76d2f712fe098c918a498add7a Initial commit

After executing the following commands:

git checkout master
git cherry-pick ac8da

Merging Branches

Branching creates parallel universes. The longer they live in parallel the harder it is to merge them together later on. When you merge a source branch into a destination branch, Git incorporates the changes made to the source branch into the destination branch. If those changes don’t overlap, the result is a new commit in the destination branch that includes everything from the source branch. Git merge is also applied for each pull request. Branching and subsequent merges are typically recommended if for longer-lasting development efforts cannot immediately be integrated. Since git merge does not affect the source tree it is also recommended if multiple developers are involved.

To merge two branches, run git merge source destination. Git automatically opens an editor so that you can write a commit/merge message for the merge, You can either keep its default message or fill in something more informative.

Rebasing

Rebasing to merge branches

From: https://git-scm.com/book/en/v1/Git-Branching-Rebasing

The easiest way to integrate the branches, as we’ve already covered, is the merge command. It performs a three-way merge between the two latest branch snapshots (C3 and C4) and the most recent common ancestor of the two (C2), creating a new snapshot (and commit), as shown below:

{width=50%}

However, there is another way: you can take the patch of the change that was introduced in C3 and reapply it on top of C4. In Git, this is called rebasing. With the rebase command, you can take all the changes that were committed on one branch and replay them on another one.

In this example, you’d run the following:

$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

It works by going to the common ancestor of the two branches (the one you’re on and the one you’re rebasing onto), getting the diff introduced by each commit of the branch you’re on, saving those diffs to temporary files, resetting the current branch to the same commit as the branch you are rebasing onto, and finally applying each change in turn.

{width=50%}

At this point, you can go back to the master branch and do a fast-forward merge:

{width=50%}

Now, the snapshot pointed to by C3’ is exactly the same as the one that was pointed to by C5 in the merge example. There is no difference in the end product of the integration, but rebasing makes for a cleaner history. If you examine the log of a rebased branch, it looks like a linear history: it appears that all the work happened in series, even when it originally happened in parallel.

Often, you’ll do this to make sure your commits apply cleanly on a remote branch — perhaps in a project to which you’re trying to contribute but that you don’t maintain. In this case, you’d do your work in a branch and then rebase your work onto origin/master when you were ready to submit your patches to the main project. That way, the maintainer doesn’t have to do any integration work — just a fast-forward or a clean apply.

Note that the snapshot pointed to by the final commit you end up with, whether it’s the last of the rebased commits for a rebase or the final merge commit after a merge, is the same snapshot — it’s only the history that is different. Rebasing replays changes from one line of work onto another in the order they were introduced, whereas merging takes the endpoints and merges them together.

Rebasing to rewrite history

You can also use git rebase to rewrite your Git commit history. Most typically, you can merge multiple commits into a single one and/or change the message of the commits. This improves readability for other developers and communication for further developments and improvements (and debugging, of course). Note, that rebasing for a nicer history is nice, but:

Do not rebase commits that exist outside your repository and people may have based work on them.

If you follow that guideline, you’ll be fine. If you don’t, people will hate you, and you’ll be scorned by friends and family.

From https://git-scm.com/book/en/v2/Git-Branching-Rebasing

Exercise

The output of git log --format=oneline on the local branch master of a specific repository looks as follows:

f54aca90848cfe7c0bb536d6d9f86cb011f5369d (HEAD) Deactivate openmp conf
6852ad0c2f8b91c2abb92108e62929c78c3827ca Returning to the original name
a1df38ffe4061d76d2f712fe098c918a498add7a Initial commit

The output of git log --format=oneline on the remote branch origin/master looks as follows:

ac8dacfb24520bbd74344ac8417232fbc113dff8 (origin/master) Changing package version
6852ad0c2f8b91c2abb92108e62929c78c3827ca Returning to the original name
a1df38ffe4061d76d2f712fe098c918a498add7a Initial commit