DEV Community

Cover image for Advanced branching in Git
Javi Palacios
Javi Palacios

Posted on • Originally published at fjp.es

Advanced branching in Git

You've already seen in previous tutorials how to create branches, switch between them, and do basic merges. Now it's time to go deeper: we'll look at the different merge strategies Git has, how to manage branches efficiently, and some special situations you'll encounter sooner or later.

Fast-forward vs non-fast-forward merges

When you merge one branch into another, Git has to decide how to do it. The simplest way is the fast-forward merge.

Fast-forward merge

Imagine you create a feature branch from master, make some commits on feature, but master hasn't moved since then. When you go back to master and merge feature, Git simply moves the master pointer to the last commit of feature:

git checkout master
git merge feature
Enter fullscreen mode Exit fullscreen mode
Updating f7c5eba..e02c63c
Fast-forward
 index.html | 2 ++
 1 file changed, 2 insertions(+)
Enter fullscreen mode Exit fullscreen mode

The Fast-forward message tells you that no merge commit was created: the pointer was just moved. The history remains linear, as if you'd never used branches.

Non-fast-forward merge (merge commit)

But if master has moved forward while you worked on feature, Git can't do a fast-forward. It has to create a merge commit that joins both lines of development:

git merge feature
Enter fullscreen mode Exit fullscreen mode
Merge made by the 'recursive' strategy.
 about.html | 10 ++++++++++
 1 file changed, 10 insertions(+)
Enter fullscreen mode Exit fullscreen mode

This merge commit has two parents: the last commit of master and the last of feature. The history is no longer linear.

Force a merge commit (--no-ff)

Sometimes you want to create a merge commit even though Git could do a fast-forward. This is useful for keeping an explicit record that there was a branch:

git merge --no-ff feature
Enter fullscreen mode Exit fullscreen mode

This makes it clear in the history that feature was a separate branch, even if master hadn't moved forward. It's common in workflows where you want to document each feature.

Force fast-forward (--ff-only)

Conversely, you can make Git only merge if a fast-forward is possible. If it's not, the command fails:

git merge --ff-only feature
Enter fullscreen mode Exit fullscreen mode

If master has moved forward, you'll see an error. This forces you to rebase before merging, keeping the history linear.

Merge strategies

Git has several strategies for merging branches. Most of the time you don't have to worry about this (Git chooses the best one), but it's good to know they exist.

recursive (default)

This is the strategy Git uses in most cases. It can handle complex scenarios where there are many changes in both branches.

ours and theirs

When there are conflicts, you can tell Git to always prefer one version:

# Always prefer changes from the current branch
git merge -X ours feature

# Always prefer changes from the branch you're merging
git merge -X theirs feature
Enter fullscreen mode Exit fullscreen mode

This is useful when you know beforehand which version you want to keep in case of conflict.

Branch management with git branch

The git branch command isn't just for creating branches. It has many options for managing them.

List branches

Without arguments, it lists all local branches:

git branch
Enter fullscreen mode Exit fullscreen mode
  feature
* master
  bugfix
Enter fullscreen mode Exit fullscreen mode

The asterisk (*) indicates which branch you're currently on.

To also see remote branches:

git branch -a
Enter fullscreen mode Exit fullscreen mode
  feature
* master
  bugfix
  remotes/origin/master
  remotes/origin/develop
Enter fullscreen mode Exit fullscreen mode

Rename a branch

If you make a mistake with a branch name (or decide to change it), use -m:

git branch -m old-name new-name
Enter fullscreen mode Exit fullscreen mode

If you're on the branch you want to rename, just use:

git branch -m new-name
Enter fullscreen mode Exit fullscreen mode

Delete a branch

Once you've merged a branch, you probably don't need it anymore. To delete it:

git branch -d feature
Enter fullscreen mode Exit fullscreen mode

Git will warn you if the branch hasn't been merged yet. If you're sure you want to delete it anyway:

git branch -D feature
Enter fullscreen mode Exit fullscreen mode

The capital -D forces deletion even if there's unmerged work.

See merged branches

To know which branches have already been merged into the current branch:

git branch --merged
Enter fullscreen mode Exit fullscreen mode

And to see those that haven't been merged yet:

git branch --no-merged
Enter fullscreen mode Exit fullscreen mode

This is useful for cleaning up old branches.

git switch: the modern alternative to checkout

Since Git 2.23 (2019) there's git switch, a command designed specifically for switching branches. Previously git checkout was used for everything, but that was confusing because checkout does too many different things.

Switch branches

git switch master
git switch feature
Enter fullscreen mode Exit fullscreen mode

It's clearer than git checkout because switch is only for changing branches, nothing else.

Create and switch to a new branch

git switch -c new-branch
Enter fullscreen mode Exit fullscreen mode

Equivalent to the old git checkout -b new-branch.

Go back to the previous branch

git switch -
Enter fullscreen mode Exit fullscreen mode

The dash (-) takes you to the branch you were on before. Very useful for going back and forth between two branches.

Should I use switch or checkout?

If your Git version is 2.23 or higher (which it should be), use git switch to change branches. Reserve git checkout for recovering files (or even better, use git restore for that).

It's more semantic and avoids confusion. Old tutorials will continue using checkout, but switch is the way forward.

Detached HEAD: what it is and how to get out

Every now and then Git will tell you you're in a detached HEAD state. It sounds alarming, but don't worry: it's a perfectly valid situation (though a bit special).

What is detached HEAD?

Normally, HEAD points to a branch (for example, master), and that branch points to a commit. When you make a new commit, the branch automatically advances.

But if you checkout a specific commit instead of a branch, HEAD points directly to the commit, not to any branch. That's detached HEAD.

git checkout e02c63c
Enter fullscreen mode Exit fullscreen mode
Note: switching to 'e02c63c'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.
Enter fullscreen mode Exit fullscreen mode

When does it happen?

  • When you checkout a specific commit (not a branch)
  • When you checkout a tag
  • When you rebase interactively and Git temporarily places you on old commits

Is it dangerous?

No, but be careful: if you make commits in this state and then switch branches without saving those commits to a branch, they'll be orphaned and eventually Git will delete them (though with reflog you can recover them).

How to get out

Option 1: Simply go back to a branch

git switch master
Enter fullscreen mode Exit fullscreen mode

Any commits you made in detached HEAD will be lost (unless you save them with reflog).

Option 2: Create a branch from there

If you made changes you want to keep:

git switch -c new-experiment-branch
Enter fullscreen mode Exit fullscreen mode

Now those commits are on new-experiment-branch and you can merge it wherever you want.

Practical cases

Maintaining a linear history

If you prefer a history without merge commits:

  1. Before merging feature into master, rebase:
   git checkout feature
   git rebase master
Enter fullscreen mode Exit fullscreen mode
  1. Then merge with fast-forward:
   git checkout master
   git merge --ff-only feature
Enter fullscreen mode Exit fullscreen mode

This keeps the history completely linear.

Cleaning up old branches

To delete all branches that have already been merged:

git branch --merged | grep -v "\*" | xargs git branch -d
Enter fullscreen mode Exit fullscreen mode

This lists merged branches, excludes the current one (grep -v "\*"), and deletes them all.

Recovering an accidentally deleted branch

If you deleted a branch and then regretted it, you can recover it with reflog:

git reflog
Enter fullscreen mode Exit fullscreen mode

Find the commit where the branch was before deletion, and create a new branch there:

git branch recovered-branch <commit-hash>
Enter fullscreen mode Exit fullscreen mode

With what you've learned in this tutorial you now have advanced mastery of branches in Git. You know how to merge in different ways, manage branches efficiently with git branch and git switch, and even how to get out of a detached HEAD without losing work.

In the next tutorial we'll look at git stash, an essential tool for when you need to quickly change context without committing half-finished changes.

Never stop coding!

Top comments (0)