Git branches

Introduction

This example uses the Lego house to help you understand branching (and merging a branch) in Git — it follows on directly from the example showing commits.

Use the next (and previous) buttons to move through the steps, and investigate the different components at each step.
If you have a keyboard, ← and → keys also work.

On the main branch

(HEAD -> main) * d234e2a - build chimney * aa29e29 - put a roof on * 64d5510 - add door+windows * 31e220a - add gables * 99ebb21 - add floor * c730af3 - add walls * 544d21f - initial commit

Start at the end of the main branch

This is where the commits example ended: the last commit added the chimney.

HEAD is pointing at that last commit (d234e2a), because it automatically advanced every time you made a commit.

Everything is committed, nothing is staged or untracked. The log shows seven commits.

To demonstrate how branches work, you’re going to go back to the previous commit (aa29e29), which is before the chimney went on.

You can find the ID of commits by looking in the log.

Go back in time

(HEAD detached at aa29e29) * aa29e29 - put a roof on * 64d5510 - add door+windows * 31e220a - add gables * 99ebb21 - add floor * c730af3 - add walls * 544d21f - initial commit

git checkout aa29e29

Checking out a previous commit is like jumping back in time: now everything is how it was when you made the commit called aa29e29.

Notice how the chimney has disappeared! You’ve gone back one commit in time to before the chimney was built.

You have a detached HEAD because HEAD is no longer attached to the end of the current branch.

Everything is committed, nothing is staged or untracked. The log shows only six commits in the history up to this point. Building the chimney is in the future: it hasn’t happened yet.

When you issued the checkout command, instead of using the commit’s ID, you could have said HEAD^, which indicates “HEAD’s parent”.

Plant some flowers

(HEAD detached at aa29e29) * aa29e29 - put a roof on * 64d5510 - add door+windows * 31e220a - add gables * 99ebb21 - add floor * c730af3 - add walls * 544d21f - initial commit

Making a different future

Planting flowers in the garden seems straightforward, but remember you’ve got a detached HEAD. This means changes you make can contradict what Git knows happens next on this branch.

This hasn’t been a problem up to now, because Git has updated HEAD every time you made a commit, so it’s always been pointing at the end of the branch. Consequently there have not been any future events to contradict.

But here Git already knows that the next change committed on this branch puts a chimney on the roof, not flowers in the garden. Committing this change will be making a different future.

If you want to change the main branch so it has flower-planting instead of chimney-building, you should stay on the main branch, and reset it to remove the chimney commit, and commit the flowers. But doing this is changing history, because you’re altering the record of what happened. This is dangerous in a repo — on a branch — that anyone else is working on.

You have a detached HEAD because HEAD is not attached to the end of the current branch.

The house is committed, nothing is staged, but the flowers are untracked. The log shows six commits in the history.

Stage the flowers

(HEAD detached at aa29e29) * aa29e29 - put a roof on * 64d5510 - add door+windows * 31e220a - add gables * 99ebb21 - add floor * c730af3 - add walls * 544d21f - initial commit

git add the-flowers

Add the new flowers to the stage.

You have a detached HEAD because HEAD is not attached to the end of the current branch.

The house is committed, the flowers are on the stage, and nothing is untracked. The log shows six commits in the history.

Commit the flowers

(HEAD detached at f44e1e7) * f44e1e7 - plant some flowers * aa29e29 - put a roof on * 64d5510 - add door+windows * 31e220a - add gables * 99ebb21 - add floor * c730af3 - add walls * 544d21f - initial commit

git commit -m "plant some flowers"

This commits the new flowers to history but this change is not on the current branch. In fact, it’s not on any branch.

You have a detached HEAD because HEAD is not attached to the end of the current branch.

The house with flowers is committed, nothing is on the stage or untracked. The log shows seven commits in the history.

Make a new branch

(HEAD -> extra-work) * f44e1e7 - plant some flowers * aa29e29 - put a roof on * 64d5510 - add door+windows * 31e220a - add gables * 99ebb21 - add floor * c730af3 - add walls * 544d21f - initial commit

git switch -c extra-work

Git knows this change (planting the flowers) isn’t on any branch, but you can fix that now by declaring a new branch, and switching to it.

Nothing in the Lego model changes, but the repo now has two branches, and you’re at the end of one where you chose to plant flowers instead of adding a chimney. You’ve called this branch extra-work.

By switching to this branch you’ve also made it the current branch: it’s the one you’re on now. (You know this because you can see flowers, but no chimney).

HEAD is no longer detached, because it’s pointing to the end of the (new) current branch, which is called extra-work.

The house with flowers is committed, nothing is on the stage or untracked. The log shows seven commits in the history of this branch, extra-work.

This repo now has two branches, and the first six commits are on both of them.

Checkout the main branch

(HEAD -> main) * d234e2a - build chimney * aa29e29 - put a roof on * 64d5510 - add door+windows * 31e220a - add gables * 99ebb21 - add floor * c730af3 - add walls * 544d21f - initial commit

git checkout main

You’ve got two branches. To demonstrate this, jump back to the first one.

Previously, you used Git’s checkout with a commit ID to go back to a previous commit. Checking out with a branch name jumps to the commit at the end of that branch.

Here, by checking out main, you’re back to how you were at the start of this example: the flowers have gone and the chimney is back!

Unlike your previous checkout, this time you have not got a detached HEAD, because checking out a branch always puts you at the end of it.

HEAD is no longer detached, because it’s pointing to the end of the branch.

The house with a chimney is committed, nothing is on the stage or untracked. The log shows seven commits in the history of this branch, main.

As you jump between different commits in the repo’s history — using checkout — Git automatically changes the bricks on the tabletop (and the files on your hard disk).

Jump back to the extra-work branch

(HEAD -> extra-work) * f44e1e7 - plant some flowers * aa29e29 - put a roof on * 64d5510 - add door+windows * 31e220a - add gables * 99ebb21 - add floor * c730af3 - add walls * 544d21f - initial commit

git checkout extra-work

Just as simply, you can jump back to the extra-work branch. Now the chimney’s gone and the flowers are back in the garden.

Let’s add a window to the roof (a dormer window).

The house with flowers but no chimney is committed, nothing is on the stage or untracked. The log shows seven commits in the history of this branch, extra-work.

Git’s checkout doesn’t only accept a commit or branch name. If you use checkout with a file or directory name, Git will reset those items to how they were in the current commit.

Make a dormer window

(HEAD -> extrawork) * f44e1e7 - plant some flowers * aa29e29 - put a roof on * 64d5510 - add door+windows * 31e220a - add gables * 99ebb21 - add floor * c730af3 - add walls * 544d21f - initial commit

Make a hole in the roof and add a window

Similarly to when you added the chimney — on the main branch — this change removes or modifies existing bricks (from the roof) and adds new ones.

To see this more clearly, hide the dormer window’s bricks and flick between everything, committed and untracked.

Adding the dormer window is an example of a change that comprises both tracked and untracked bricks: the roof tiles are already in version control.

A difference between Lego brick and code changes: in practice you can’t change Lego bricks’ colour or size (only remove them and replace them with new ones), but the analogy is still useful.

The house with flowers but no chimney is committed, nothing is on the stage but some bricks are modified and others are untracked.

Stage the changes

(HEAD -> extra-work) * f44e1e7 - plant some flowers * aa29e29 - put a roof on * 64d5510 - add door+windows * 31e220a - add gables * 99ebb21 - add floor * c730af3 - add walls * 544d21f - initial commit

git add dormer window

This puts the changes you’ve made to the roof and the window into the staging area, ready to commit.

It is possible to commit directly, bypassing the stage, by specifying the bricks (files) directly. But when you’re getting familiar with Git, using the stage is a good discipline to get into.

The house and flowers is committed, the changes to the roof and the new window are staged, nothing is untracked.

The current branch is extra-work.

Commit the dormer window

(HEAD -> extra-work) * 1e8f0f2 - add dormer window * f44e1e7 - plant some flowers * aa29e29 - put a roof on * 64d5510 - add door+windows * 31e220a - add gables * 99ebb21 - add floor * c730af3 - add walls * 544d21f - initial commit

git commit -m "add dormer window"

The changes to the roof are now committed.

Everything is committed, nothing is staged or untracked. The current branch is extra-work.

In the next step, you’re going to merge the extra-work branch into main.

Switch back to the main branch

(HEAD -> main) * d234e2a - build chimney * aa29e29 - put a roof on * 64d5510 - add door+windows * 31e220a - add gables * 99ebb21 - add floor * c730af3 - add walls * 544d21f - initial commit

git checkout main

You’re about to merge the changes you’ve made on the extra-work branch into the main branch.

There’s more than one way to do this, but the simplest is to make main your current branch, and then merge the changes into it.

You’re back on the main branch, so the house with the chimney is committed, and nothing is staged or untracked.

Merge extra-work and get a conflict

(HEAD -> main|MERGING) * d234e2a - build chimney * aa29e29 - put a roof on * 64d5510 - add door+windows * 31e220a - add gables * 99ebb21 - add floor * c730af3 - add walls * 544d21f - initial commit

git merge extra-work

When you attempt to merge these two branches — by bringing the commits from extra-work into main — you get a conflict. Git tells you:

Auto-merging Lego house
CONFLICT (content): Merge conflict in Lego house
Automatic merge failed; fix conflicts and then commit the result.

The first commit (f44e1e7: plant the flowers) isn’t a problem because the flowers don’t interfere with the changes that built the chimney (d234e2a).

But Git can’t merge the changes that are happening on the second commit, (1e8f0f2: add dormer window). There’s a conflict because you’re trying to put different bricks in the same space, or change the same bricks differently. Git won’t let you have a history where both these changes happened... because that would be impossible.

The repo has unmerged paths. Git is waiting for you to fix the conflict, and commit that fix, to complete the merge.
† “staged” isn’t quite right here because the repo is in an intermediate state (see the next steps).

The next steps show three ways you might fix the conflict, and why Git cannot decide how to solve this automatically.

If you can’t fix the conflict, you can abort it with:
git merge --abort
This will put the repo back to the state it was in before you attempted the merge, with two branches each ending on separate commits.

Solution 1: forget the dormer

Resolve the conflict by discarding the dormer

One way to fix this problem is to abandon putting a window in the roof. This is OK provided you didn’t really want it.

To implement this, you could checkout the roof change using --ours, which accepts the changes in the branch you’re on (see the next step for the alternative, --theirs):

$ git checkout --ours rooftop
Updated 1 path from the index
$ git add rooftop
$ git commit

Note that the flowers did not generate a conflict. So the merge still includes that commit (planting the flowers).

If you’re working with text files not Lego, use filenames instead of bricks.

Solution 2: lose the chimney

Resolve the conflict by discarding the chimney

Alternatively, you could keep the dormer in favour of the chimney. Maybe the occupants weren’t going to use the heating anyway.

This time could checkout the roof change using --theirs, which accepts the changes in the branch you’re bringing in (see the previous step for the alternative, --ours):

$ git checkout --theirs rooftop
Updated 1 path from the index
$ git add rooftop
$ git commit

Note that the flowers did not generate a conflict. So the merge still includes that commit (planting the flowers).

If you’re working with text files not Lego, use filenames instead of bricks.

Solution 3: move the chimney

Resolve the conflict by making new changes

To make this change, the chimney needs to be repositioned, and extra bricks — that were not in either of the changes you’re merging — will be needed.

This time you can’t use --ours or --theirs since you’re making a change that isn’t only one or the other.

Just like other changes, you can experiment, test, then add to the stage, and when you’re ready, commit the merge. It’s just like any other commit, except it’s got more than one parent commit.

The next steps complete the merge with this third solution.

Resolve conflict: make and stage the changes

(HEAD -> main|MERGING) * d234e2a - build chimney * aa29e29 - put a roof on * 64d5510 - add door+windows * 31e220a - add gables * 99ebb21 - add floor * c730af3 - add walls * 544d21f - initial commit

git add chimney-changes

To move the chimney to the outside of the house, you must edit the changes that the merge process has made before you stage them.

Until you resolve the conflict, those changes are impossible. Some chimney bricks occupy the same space as dormer window bricks. Some roof bricks have changed in two different ways (they got shorter and stayed on the roof, and were removed altogether).

If you’re working with text files, Git will report changes in the same file that are within one line of each other as a conflict. Usually this is fine, but it is possible to specify custom merge strategies and drivers if your project requires more precision.

The house with chimney is committed (from the most recent commit on the current branch), the resolved changes for this merge are staged, nothing is untracked. The log shows seven commits on the current branch.

Complete the merge

(HEAD -> main) * e0cfbaf - merge dormer by moving chimney * d234e2a - build chimney * aa29e29 - put a roof on * 64d5510 - add door+windows * 31e220a - add gables * 99ebb21 - add floor * c730af3 - add walls * 544d21f - initial commit

git commit -m "merge dormer by moving chimney"

This commits the fixes that you staged: the commit you just made is a merge commit. The extra-work branch has now been merged into main.

The repo still has two branches — and most of the commits in its history are on both. Merging does not delete branches!

Everything is committed, nothing is staged or untracked. The log contains 8 commits (including the most recent merge commit).

The latest commit (e0cfbaf) is the end of both branches. The current branch is main. If you were to checkout the extra-work branch, the only immediate change would be the history: the committed bricks are the same.