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
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
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
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
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
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
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 switch
ing 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
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
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
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.
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
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
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
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
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
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
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.