6  Branches

beginner
basics
Summary

In this chapter you will learn about the power of branches. Create, manage and merge branches like a pro!

Learning Objectives

💡 You understand the purpose and benefits of using branches in Git
💡 You can create and switch between branches
💡 You can merge branches and resolve merge conflicts
💡 You can name at least three best practices when working with branches

Take the quiz!

6.1 Why branches?

In short, branches in Git allow you to keep different versions of your repository content in parallel, develop them independently, and integrate them when they are ready. Before we explain the concept of branches in more detail, let’s look at a few scenarios that illustrate why branches are useful (taken from The Turing Way Community 2022):

  1. You want to add a new feature to your project (for example, a new analysis) but while doing so, you run the risk of accidentally breaking your working code as you test the new feature. You may want to quickly go back to your previous working version while working on the new feature but keep the as yet unfinished progress on the new feature.

  2. If you collaborate with others on the same Git repository (we will look at collaboration with Git in the chapter on Git remotes) and everyone would work on the main branch simultaneously, this could cause a lot of confusion and conflicting changes.

6.2 What are branches?

By default, every Git repository has one default branch, typically called main or master. Git branches are like separate versions or workspaces within the same Git repository. Branching allows to create a copy of a project and continue to make commits without integrating them into the default branch right away. Branches let you work on a parallel version of your repository’s contents without changing the content on your default branch (your main or master branch). You can visualize branches as shown in Figure 6.1.

Each branch keeps its own history of changes, so you can develop things in parallel and merge changes between branches when you’re ready. Even if you are not happy with what you’ve developed on a branch, you can just delete or abandon the branch and continue with what you have on the default branch (see the “Feature B” branch in Figure 6.1). You can have as many branches as you want and also branch off from a branch that branched off from the default branch (see the “Feature A-1” branch in Figure 6.1).

Branches are great for trying out new features, fixing bugs, or experimenting without causing chaos in the main development branch. Scientists might use branches to collaborate on research papers, exploring different parameters while running experiments or to test different data processing techniques or visualization approaches during data analysis. Git makes it easy to switch between branches and handle conflicts when merging. With branches, you can work independently and keep your code organized.

Figure 6.1: Illustration of branches in a Git repository. Each circle represents a commit. Commits on the Main branch are shown in gray. The branch Feature A (blue circles) was created from the Main branch and later merged again. In-between, another branch Feature A-1 was created from the Feature A branch (green circles) and merged with that branch again before the Feature A branch was eventually merged with the Main branch. The branch Feature B was created but abandoned and not merged with the Main branch. Image from Chapter “Git Branches” of the “The Turing Way handbook to reproducible, ethical and collaborative data science”, used under a Creative Commons Attribution 4.0 License.

6.3 Checking your branches

By default, your work is on the default branch, usually called main (or master). We recommend changing your default branch name to main, as discussed in the setup chapter. You can list the available branches in your repository by using git branch:

Code
git branch 

This should return the following output, if you have not yet created a new branch:

Output
* main

This output indicates that you are currently “on the branch” named main. The asterisk (*) in front of the branch name indicates the active or current branch in your Git repository.

In Git, you can have multiple branches in your project, and the git branch helps you see the list of available branches. The one with the asterisk is the branch where your working directory is currently positioned. In this case, main is the active branch. If there were other branches listed without an asterisk, it means they exist, but you are not currently “on” those branches. You are currently “on main”.

You can also use git status to check which branch you are on. git status will return an output similar to the following:

Output
On branch main
nothing to commit, working tree clean

6.4 Creating a new branch

To create a new branch, use git branch followed by a branch name. For example, to create a new branch called feature run the following command:

Code
git branch feature

Now you can use git branch again to confirm the creation of the new branch.

Code
git branch 

This should return the following output:

Output
  feature
* main

Your newly created branch (here, feature) will show up, but your active branch should still be main. Note that feature is just a name. You could also call your branch banana, if you want. Find out more about naming convention for Git branches in the section on best practices below.

Common git branch flags

-a or --all: Lists both local and remote branches. (Remote branches will be introduced in the Remotes: Introduction chapter )

-r or --remote: Lists only remote branches.

-d or --delete: Deletes a specified branch. For example, git branch -d feature.

-D or --force: Force deletes a branch, even if it has unmerged changes.

-m or --move: Renames a branch. For example, git branch -m old-branch new-branch.

-c or --copy: Creates a new branch by copying an existing branch. For example, git branch -c existing-branch new-branch

-v or --verbose: Shows more information when listing branches, including the last commit message.

6.5 Switching branches

To switch to another branch, you can use git checkout or git switch. For example, enter the following command to switch to the newly created feature branch:

Code
git switch feature

If you switched branches successfully, you should receive an output similar to this:

Output
Switched to branch 'feature'

Now you can work on this branch as usual, staging and committing changes, as introduced in the chapter on basic Git commands.

Before you can switch branches for the first time, you need to have made at least one commit on your current branch (for example, on the main branch). This requirement ensures that there’s a baseline history in your repository, allowing Git to manage changes.

Prior to Git version 2.23, the git checkout command was the primary way to switch branches. It allows you to move to a different branch and update your working directory to reflect the state of that branch. In Git 2.23 and later versions, the git switch command was introduced as a safer and more intuitive alternative to git checkout. It simplifies the branch-switching process and provides clearer feedback in case of errors.

git checkout is a versatile and powerful command that, among other things, was traditionally used for switching branches. However, it has other use cases like checking out specific files or commits. It can be used for branch switching, but it may have some ambiguity in its syntax, especially when used for other purposes.

6.5.1 Switching branches with uncommitted changes

This section references more advanced Git commands. If you’re facing the issue that Git prevents you from switching branches, read on. If you’re just learning about branches and switching branches worked without any problems, feel free to skip this section for now.

When you have uncommitted changes in your working directory that do not conflict with the branch you’re switching to, Git allows you to switch branches without losing those changes. Your uncommitted changes are effectively brought over to the new branch. This feature can be particularly useful when you’ve started making changes and then realize you’re on the wrong branch or need to move your work to a different context.

Git is designed to prevent you from switching branches if there’s a risk of overwriting your changes. This means that Git will not allow you to switch branches if your uncommitted changes would cause conflicts with the branch you’re attempting to switch to. If you try to switch branches with uncommitted changes you will get an error message similar to:

Output
error: Your local changes to the following files would be overwritten by checkout:
        example.txt
Please commit your changes or stash them before you switch branches.
Aborting

The message specifies the file with modifications (here, example.txt), and indicates an aborted branch switch due to uncommitted changes. To resolve this and switch branches safely, you have a few options:

  1. Commit your changes: Use the stage and commit process to save your work on the current branch. This involves staging your changes with git add and then committing them with git commit. This workflow is explained in detail in the chapter on basic Git commands.

  2. Stash your changes: If you’re not ready to commit, you can use git stash to temporarily shelve your changes. This clears your working directory, allowing you to switch branches. Your stashed changes can be reapplied later. Stashing is covered in more detail in the section below.

  3. Discard your changes: If the changes are not needed, you can remove them using git reset to unstage all changes. This action should be used with caution as it will erase your modifications. This command is covered in the chapter “Rewriting history”.

6.6 Merging branches

After you’ve worked on a branch for some time and you completed work on the feature that you wanted to implement, you might want to incorporate your work into the main branch (or any other target branch). You can do this using the git merge command. To do this, switch to your main branch (using git switch or git checkout) and then use:

Code
1git merge feature
1
Replace feature with the name of your branch, if needed.

Note, that your current branch (the branch that you are “on”) should be the one that you want to merge changes into from another branch. For example, if you want to merge changes into the main branch from the feature branch, you need to be on main and then merge the feature branch.

If you have not yet made any changes on the feature branch you should get the following output:

Output
Already up to date.

This output indicates that the branch you are trying to merge is already fully incorporated into the branch you are currently on. In other words, there are no new changes in the feature branch that need to be merged because your current branch already contains all the changes from the feature branch.

If you have changed file(s) in your folder and committed them on the feature branch, you’ll likely get an output similar to:

Output
Updating 555ba0c..994bb8d
Fast-forward
 
 example.txt    |   4 ++++

 1 file changed

The output reveals a “fast-forward” merge operation, where changes from commit 555ba0c to 994bb8d are incorporated. The common starting point of the two branches is commit 555ba0c and commit 994bb8d marks the latest commit in the merged branch. A “fast-forward” merge, the simplest form of merges, updates your position to the latest changes without creating a new commit. In this example, the merge modified only one file, the example.txt file, introducing four new lines denoted by 4 ++++. For more information about different types of merges, see the section on “Types of merges” below.

6.7 Merge conflicts

Merge conflicts occur when Git is unable to automatically resolve differences between two branches during a merge. This happens when both branches include conflicting changes to the same part of a file. Git simply can’t determine which version of the code should be favored, and as a result, it requests manual intervention from you, the developer. It’s similar to when two people try to edit the same part of a file at the same time, and Git gets confused about whose changes to keep. Merge conflicts are more likely to occur in collaborative projects, where multiple people modify the same files. However, they can also happen in projects where you work on your own.

6.7.1 Avoiding merge conflicts

Merge conflicts can be a bit of a headache. If you only work locally on your computer, without a remote repository (for example, an online version of your repository on GitHub or GitLab), you can avoid merge conflicts easier because you are less likely to edit the same part of the same file on different branches. However, once you start to collaborate with others, it may not be possible to entirely avoid merge conflicts. That being said, employing good development practices can minimize the occurrence of merge conflicts. Regularly pulling changes from the remote repository into your local branch (using git pull) and keeping your branch up-to-date can help identify and resolve conflicts early. You will learn more about how to effectively manage collaborative work on Git repositories in the following chapters on remote repositories.

6.7.2 Example merge conflict

Let’s look at an example merge conflict. You can recreate this example yourself by following the instructions below.

  1. Create a new file, for example called example.txt.
  2. Create and switch to a new branch, for example called feature.
  3. Add some text to the first line of the example.txt file, for example Git is super.
  4. Add and commit your changes on the feature branch.
  5. Switch back to your main branch.
  6. Add some text to the first line of the example.txt file, for example Git is cool. Note that example.txt should be empty because the previous text was added on the feature branch, not on the main branch.
  7. Add and commit your changes on the main branch.
  8. Merge the changes on the feature branch into the main branch: git merge feature.
  9. Congrats, you create a merge conflict!

6.7.3 Understanding merge conflicts

When a merge conflict arises, you should not view it as a limitation of Git but rather as a helpful feature. If you try to merge branches with conflicting changes in a file the output looks similar to this:

Output
Auto-merging example.txt
CONFLICT: Merge conflict in example.txt
Automatic merge failed; fix conflicts and then commit the result.

During a merge conflict, you can run git status to see which files are in conflict:

Output
On branch main
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
    both modified:   example.txt

no changes added to commit (use "git add" and/or "git commit -a")

Git will highlight the conflicting parts in the affected file(s), and you must choose the desired changes or modify them to create a consistent version. To do this, open up the file(s) with conflicts in a text editor of your choice, and you’ll see the conflicting sections marked with the following indicators:

  • <<<<<<<
  • =======
  • >>>>>>>

In our example, this will look like this:

Output
<<<<<<< HEAD
Git is cool
=======
Git is super
>>>>>>> feature

<<<<<<< indicates the beginning of lines that encountered a merge conflict. The initial lines are from the file where you were attempting to merge the changes (in this case, the changes on the main branch). The lines between <<<<<<< HEAD and ======= represent the changes that were made in your current branch (here, the main branch). ======= shows the break point for comparison. It divides the changes the user has committed (above) from the changes brought in from the merge (below) for visual comparison. The section between ======= and >>>>>>> represents the changes from the branch you are trying to merge into your current branch (here, the feature branch). >>>>>>> shows the end of the lines with a merge conflict.

6.7.4 Resolving merge conflicts

To resolve the merge conflict, you need to manually edit the conflicting sections to the desired state, removing the conflict markers. You might want to keep some changes from your current branch, some from the merged branch, keep only the changes from one branch, or make entirely new changes. After editing the file it might look something like this:

Output
Git is super cool!

In this example, the conflict has been resolved by mixing both sets of changes. Parts of the changes from both branches are now present in the file. If you are done you can use git add to stage the resolved changes:

Code
git add example.txt

Once all conflicts are resolved, proceed with the commit, for example:

Code
git commit -m "Resolve merge conflict"

Your branches should now be merged and your conflict resolved.

Before you try to fix more complex merge conflicts, make sure you understand the changes that don’t match up to avoid making things more complicated. Resolving conflicts can be tricky, especially when merging branches with many differences. But remember that your old versions are safe, so you can fix the issue without affecting them.

6.7.5 Aborting a merge

If you feel like you are stuck in the merge process and want to get back to the state of the branch before you started the merge process, you can also abort the merge using:

Code
git merge --abort

--abort: Abort the current merge operation and reset the branch to its pre-merge state.

--continue: Continue the merge process after resolving conflicts.

--log: Include the log message from the commit(s) being merged in the new merge commit.

6.7.6 Types of merges

Git supports different types of merges to support various situations in software development. Each merge type has its purpose and advantages, allowing developers to choose the most suitable approach based on their specific project needs and workflow.

6.7.6.1 Standard merge

In a standard merge, Git takes each commit from the merged branch and adds them to the history of the base branch (the branch you are currently on) with their original time stamps. It creates a new “merge commit”, a special record that marks when the merge happened, showing that the branches are now combined. To perform a standard merge, use the following command:

Code
git merge 

6.7.6.2 Fast-forward merge

If no new commits were made to the base branch since the feature was created, Git automatically does a “Fast Forward Merge”. It’s like the standard merge, but no new merge commit is created. It’s as if you made the commits directly on the base branch. Since the base branch remained unchanged, there’s no need to mark a separate merge in the history. You can ensure a fast-forward merge by using the --ff-only flag:

Code
git merge --ff-only

If you do not want a fast-forward merge, you can ensure a merge commit with the --no-ff flag.

Code
git merge --no-ff

6.7.6.3 Squash and merge

When you squash a branch, Git combines all commits of the branch into a single commit. This new commit is then added to the history, but the individual commits that were part of the branch are not retained or preserved separately. Instead, the squashed commit represents the collective changes made in the branch, creating a cleaner and more concise history.

Code
git merge --squash

Squashing commits can be desirable in a situation where you don’t want others to see the history of individual commits made on the feature branch. For example, imagine writing the draft of a paper and you’ve made a couple of changes in the early phase of writing the draft that you consider silly and you only want your collaborators to see the final draft version. In this situation, you could squash all previous commits and only merge the latest version of your draft.

6.8 Deleting Branches

Once a branch has served its purpose and is no longer needed, it can be deleted. Removing branches that are no longer active helps maintain a clean and manageable branch structure. To delete a branch you can use the git branch command, followed by a -d flag.

Code
git branch -d feature

This should yield an output similar to:

Output
Deleted branch feature.

You can only delete a branch, you are not currently on.

6.9 Best practices

6.9.1 Keep the main branch clean

The central purpose of branches is to keep the main branch clean. In practice, this means that you should regularly work on separate branches and only integrate new changes into the main branch when they are ready. The main branch should only contain work that is complete and rightfully belongs in the main version of your project. For example, if you use Git to track changes in code (like analysis scripts) you should make sure that the version of the code on the main branch always works, i.e., can be executed without errors. Any addition to your analysis code would be developed on a separate branch and only integrated with the main branch if the code can be executed without errors.

6.9.2 Name branches consistently

Adopt a consistent naming convention for branches that accurately reflects their purpose. This can include prefixes like feature/, bug/, followed by a descriptive name. Clear and standardized branch names make it easier to identify the purpose of each branch.

Basic recommendations for branch names

  1. Lowercase and hyphen-separated: Use lowercase letters for your branch names and hyphens to separate words. For example: feature/new-login or bugfix/header-styling.
  2. Alphanumeric characters: Limit branch names to alphanumeric characters (a-z, 0-9) and hyphens, avoiding punctuation, spaces, underscores, or other non-alphanumeric characters.
  3. No continuous hyphens: Avoid using continuous hyphens in branch names, as in feature--new-login, which can be confusing.
  4. No trailing hyphens: Refrain from ending branch names with a hyphen, as in feature-new-login-.
  5. Descriptive: Choose descriptive and concise names that reflect the focus of the work carried out on the branch.

Branch prefixes

Using prefixes in branch names helps to quickly identify the purpose of a branch. Below are two common branch types with their corresponding prefixes:

  • Feature Branches: These branches are used for developing new features. Use the prefix feature, for example: feature/new-analysis.
  • Bugfix Branches: These branches are used to fix bugs (errors or problems with the code). Use the prefix bugfix, for example: bugfix/fix-typo.

For more recommendations on branch naming conventions, see for example Amin (2023).

6.9.3 Keep branches specific and short-lived

Create branches with a specific goal or task in mind, and aim to keep them short-lived. As for your main branch, you should try to keep individual branches clean by focusing on adding one new feature per branch. This approach ensures that completed features can be merged into the main branch independently of those that are still in progress. Long-lived branches can lead to conflicts and make it harder to merge changes. Once the task is complete or the feature is integrated, consider deleting the branch.

6.9.4 Avoiding merge conflicts

The best way to deal with merge conflicts, is to try and avoid them in the first place. You can reduce the chances of conflicts by keeping branches tidy, concentrating on one task, and involving fewer files. In addition, it’s good practice to merge each other’s changes into your work frequently. Before merging, understand the content in both branches. If multiple people have worked on the same branches, open communication and good project management can also help to stay informed about each other’s changes.

This section concludes the most important Git commands for working with branches for new learners. The following sections will dive into more advanced Git commands that become relevant once you start using branches more extensively. If you’re just learning about branches and the commands above worked without any problems, feel free to skip to the next chapter for now.

6.10 Stashing

git stash is a valuable command in Git that allows you to save your current changes temporarily without committing them. This is useful when you need to switch branches or work on something else without creating a commit for unfinished work. For example, when you’re working on a feature and you need to switch to a different task quickly or your work gets interrupted unexpectedly, you may not want to commit your unfinished changes. When you run git stash, Git stores the changes in your working directory and staged changes in a special stash commit. The working directory is then reverted to the last committed state, providing a clean slate for your next task.

6.10.1 Stashing changes

To demonstrate the usefulness of git stash, you can edit one or more files in your repository, stage (or don’t stage) your changes, but don’t commit them. Now use git status to look at the current state of your working directory.

Code
git status

You should get an output similar to:

Output
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   example.html

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   example.txt   

In this example, there are changes on two tracked files, one staged and one not staged, that we want to stash. Just like when committing, it is possible to add a message when stashing using the -m flag. It makes sense to add a message because it serves as a reminder of what changes were stashed, making it easier to identify the purpose of the stash when you later list or apply stashes.

Code
git stash -m "feature X WIP"

You should get an output similar to:

Output
Saved working directory and index state On feature: "feature X WIP"

You can now use git status to look at your working directory again and should see an output similar to:

Output
On branch main
nothing to commit, working directory clean

Now you can switch branches and work on something else or stop working on the project altogether.

6.10.2 Retrieving stashed changes

To look at all your stored stashes, you can use git stash list:

Code
git stash list

You should get an output similar to:

Output
stash@{0}: On feature: feature X WIP

All of your stored stashes should show up, with the most recent on top. Every stash should have a number, applied chronologically.

6.10.3 Applying stashed changes

If you want to reapply your stashed changes, you can use git stash apply. This command automatically applies your latest stash. You can also specify a stored stash, for example git stash apply stash@{0}

Code
git stash apply stash@{0}

You should get an output similar to:

Output
On branch main
Changes not staged for commit:
  (use "git add <file>.. ." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   example.html
    modified:   example.txt

no changes added to commit (use "git add" and/or "git commit -a")

The changes made in your stored stashed are now applied again in your working directory. Note that already staged files are not automatically staged again.

git stash -m "message": Save your changes in a stash with a message.

git stash apply stash@{n}: Apply the changes from the specified stash (identified by its index n) to your working directory.

git stash pop stash@{n}: Apply the changes from the specified stash (identified by its index n) to your working directory and remove the stash from the stash list.

git stash list: List all the stashes you have created, showing their reference numbers and stash messages.

git stash drop stash@{n}: Remove the specified stash (identified by its index n) from the stash list.

git stash branch <branchname>: Create a new branch named <branchname> from the commit where you originally stashed your changes and apply the stash to the new branch.

git stash show stash@{n}: Show the diff of the changes stored in the specified stash (identified by its index n).

6.11 Alternatives to standard merging

6.11.1 Cherrypicking

git cherry-pick allows you to apply the changes from a specific commit from one branch to another. This means, you can pick and apply specific commits to your current branch without merging the entire branch. This can be useful when you only want to bring in specific changes from another branch into your current branch, in contrast to merging all commits of a branch, as visualized in Figure 6.2. You will need the hash of the commit you want to “cherry-pick” and then use the command:

Code
git cherry-pick <commithash>

Again, you might have to resolve merge conflicts. The default commit message will be:

Output
Cherry-pick commit <commit-hash>
This commit was cherry-picked from <source-branch> at <source-commit-hash>
Figure 6.2: Image from Blogpost “What IS git cherry-pick?” by 0xkoji

-n or --no commit: Prevents Git from automatically creating a new commit after cherry-picking. It stages the changes, allowing you to make additional modifications or review them before committing.

-e or --edit: Opens the default text editor to edit the commit message of the new cherry-picked commit. Useful when you want to provide a custom message for the cherry-picked commit.

6.11.2 Rebasing

git rebase is a different way compared to a standard merge to integrate changes from one branch into another. For example, when you rebase the feature branch onto the main branch you “rearrange” the commits. It’s like taking your changes, applying them on top of the latest main branch, and making it all look like a smooth line. The new commits you made in the feature branch are still there, but they appear as if they were created after the latest changes in the main branch. It’s like picking up your changes and placing them on the latest code, resulting in a linear history. For an illustration, see Figure 6.3.

Code
git rebase main

However, you should use rebase with caution when collaborating with others, as it can rewrite commit history and create conflicts for team members.

--interactive or -i: Start an interactive rebase, allowing you to edit, reorder, or squash commits interactively.

--continue: Continue the rebase after resolving conflicts or editing commits during an interactive rebase.

--abort: Abort the current rebase operation and return the branch to its original state before the rebase.

--skip: Skip the current commit during an interactive rebase.

-p or --preserve-merges: Preserve merge commits during the rebase.

--autosquash: Automatically squash commits marked with “squash” or “fixup” in their commit message during an interactive rebase.

6.12 Acknowledgements & further reading

We would like to express our gratitude to the following resources, which have been essential in shaping this chapter. We recommend these references for further reading:

Authors Title Website License Source
The Turing Way Community (2022) The Turing Way: A handbook for reproducible, ethical and collaborative research CC BY 4.0
Chacon and Straub (2014) Pro Git CC BY-NC
GitHub (2023) GitHub Docs CC BY-NC 4.0
Amin (2023) Naming conventions for Git Branches — a Cheatsheet NA

6.13 Cheatsheet

Command Description
git branch Lists / creates and deletes branches
git switch Switches between branches
git checkout Switches between branches
git merge Merges branches
git merge --abort Aborts a merge
git merge --squash Squaches commits on branch into a single commit and merge
git branch -d feature Deletes the feature branch
git stash Stores made changes for later use
git stash -m "stashing message" Stashes your changes and includes a message
git stash list Shows all of your stored stashes
git stash apply Applies your latest stash
git stash apply stash@{n} Applies a specific stash
git stash pop Applies your latest stash and removes it from stash list
git stash pop stash@{n} Applies a specific stash and removes it from stash list
git cherry-pick <commithash> Applies changes from <commithash>
git rebase Different way of integrating changes from two branches