1.4 Merging Branches#
In this section, we’ll see how to integrate work from the feature
branch back into the main
branch.
Types of Merges#
The two most common merge strategies are fast-forward and three-way merges:
Fast-forward merge: This merge strategy is used when the target branch can be updated to point to the commit of the source branch without needing to create a new commit.
Three-way merge: A three-way merge is used when the two branches have diverged and a new commit is required to reconcile the differences.
Fast-forward merge#
Let’s first understand visually how a fast-forward merge of the feature
branch into main
would look like by comparing the current state of the repository with the state after the merge.
This is how the repository looks like now:
How can we incorporate the changes from feature
into main
? If we look at the commit history, we can see that main
is behind feature
by one commit. To update main
with the changes from feature
, we can simply move the main
pointer to the same commit as feature
. This is possible whenever you can update a branch to match another by advancing its pointer through the commit history until it reaches the same commit. This process is known as a ‘fast-forward’ because it involves moving the pointer forward to the commit of the other branch without creating a new merge commit.
So after the fast-forward merge, the repository would look like this:
What to notice
Since
main
was behindfeature
by one commit, themain
pointer was simply moved to the same commit asfeature
and now it points toC3
.feature
still points toC3
.HEAD
now points tomain
since in order to do the merge we had to switch first tomain
.
Now that we understand what a fast-forward merge is, let’s see how to do it in Git. To perform a fast-forward merge, first switch to the branch you want to merge into, and then merge the other branch into it using git merge <branch-name>
. For example, to merge feature
into main
:
git switch main
Switched to branch 'main'
git merge feature
Updating d30b4a0..4c34d2d
Fast-forward
file1.txt | 1 +
1 file changed, 1 insertion(+)
What to notice
Git tell us that there was one file changed, file1.txt, and that there was 1 insertions (which we know is the third line).
git status
On branch main
nothing to commit, working tree clean
After the fast-forward merge, there are no changes to commit, the working tree is clean.
git log
commit 4c34d2de5a3d346a271ea3c30477f0049ddc88cc (HEAD -> main, feature)
Author: msdp-book <msdp.book@gmail.com>
Date: Fri Mar 15 19:16:24 2024 +0100
Add third line to file1.txt
commit d30b4a05bd3582bd7ec537c09ed0c764cc605e1e
Author: msdp-book <msdp.book@gmail.com>
Date: Fri Mar 15 19:16:22 2024 +0100
Add second line to file1.txt
commit 2007711caa39b5ad6bee678519eb0b98e71bd1a3
Author: msdp-book <msdp.book@gmail.com>
Date: Fri Mar 15 19:16:17 2024 +0100
Create file1.txt with the first line
We see that all the changes that we discussed in the diagram have been made. The main
branch now points to the same commit as feature
, and the feature
branch still points to the same commit. The HEAD
pointer is now pointing to main
.
Impact of switching branches on the working directory#
We will now explore what happens to the working directory when we switch branches. To see this, let’s first add a new file and make changes to an existing file on the feature
branch, and then switch to the main
branch.
git switch feature
touch file2.txt
echo "This is first line." >> file2.txt
echo "This is the fourth line." >> file1.txt
git add .
git commit -m "Add file2 and modify file1"
Switched to branch 'feature'
[feature ae47e9c] Add file2 and modify file1
2 files changed, 2 insertions(+)
create mode 100644 file2.txt
ls
cat file1.txt
file1.txt file2.txt
This is the first line.
This is the second line.
This is the third line.
This is the fourth line.
git switch main
Switched to branch 'main'
ls
cat file1.txt
file1.txt
This is the first line.
This is the second line.
This is the third line.
What to notice
We see that the file
file2.txt
is no longer in the working directory after switching tomain
.The changes made to
file1.txt
on thefeature
branch are removed after switching tomain
.
Git updates the files in your working directory to reflect the state of the files in the new branch. This means that any files unique to the previous branch but not present in the new one may be removed, and any files unique to the new branch may be added or modified.
Switching branches with uncommitted changes#
Git prevents loss of work by disallowing branch switching with uncommitted changes. To see this in action, let’s make changes to file1.txt
on the feature
branch and then try to switch to main
without committing the changes.
git switch feature
echo "This is the fifth line." >> file1.txt
git switch main
Switched to branch 'feature'
error: Your local changes to the following files would be overwritten by checkout:
file1.txt
Please commit your changes or stash them before you switch branches.
Aborting
What to notice
Git does not allow branch switching with uncommitted changes to protect against data loss.
Modifying an staged file#
Changes made after staging are not automatically included in the staged snapshot. To include changes in the staged snapshot, the file must be staged again.
git add file1.txt
echo "This is the sixth line." >> file1.txt
git status
On branch feature
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: file1.txt
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: file1.txt
What to notice
The file
file1.txt
is staged and has uncommitted changes at the same time.
We can also see the status in a more compact form using the -s
option:
git status -s
MM file1.txt
The first character in the output of git status -s
shows the status of the staged snapshot, and the second character shows the status of the working directory.
What to notice
As we saw before, with
git status
,file1.txt
has both staged and not staged modifications.
Let’s now stage the latest changes to file1.txt
git add file1.txt
git status -s
M file1.txt
now all modifications has been staged and we can commit them:
git commit -m "Add fifth and sixth lines to file1.txt"
[feature 73aa479] Add fifth and sixth lines to file1.txt
1 file changed, 2 insertions(+)
Viewing all commits#
When working with multiple branches in Git, you might find yourself on a branch that is behind others in terms of commits. For instance, if you’re on the main
branch that hasn’t been updated with the latest commits from a feature
branch, using the git log
command while on main
will only show the commit history up to the point main is currently at. This means you won’t see the commits that are on feature
or any other branch until they are merged into main
.
This is because git log
displays by default only the commit history of that branch, starting from the current commit and tracing back through the history of commits that lead to the current state of the branch.
Let’s see this in action by using the git log
command while on the main
branch.
git switch main
git log
Switched to branch 'main'
commit 4c34d2de5a3d346a271ea3c30477f0049ddc88cc (HEAD -> main)
Author: msdp-book <msdp.book@gmail.com>
Date: Fri Mar 15 19:16:24 2024 +0100
Add third line to file1.txt
commit d30b4a05bd3582bd7ec537c09ed0c764cc605e1e
Author: msdp-book <msdp.book@gmail.com>
Date: Fri Mar 15 19:16:22 2024 +0100
Add second line to file1.txt
commit 2007711caa39b5ad6bee678519eb0b98e71bd1a3
Author: msdp-book <msdp.book@gmail.com>
Date: Fri Mar 15 19:16:17 2024 +0100
Create file1.txt with the first line
Now compare this with the output of git log --all
:
git switch main
git log --all
Already on 'main'
commit 73aa47900ddb4b5cf6a6fcc222bb6e8f73bc16f4 (feature)
Author: msdp-book <msdp.book@gmail.com>
Date: Fri Mar 15 19:16:33 2024 +0100
Add fifth and sixth lines to file1.txt
commit ae47e9cc31a286967d519a32f7f089776ee1223e
Author: msdp-book <msdp.book@gmail.com>
Date: Fri Mar 15 19:16:31 2024 +0100
Add file2 and modify file1
commit 4c34d2de5a3d346a271ea3c30477f0049ddc88cc (HEAD -> main)
Author: msdp-book <msdp.book@gmail.com>
Date: Fri Mar 15 19:16:24 2024 +0100
Add third line to file1.txt
commit d30b4a05bd3582bd7ec537c09ed0c764cc605e1e
Author: msdp-book <msdp.book@gmail.com>
Date: Fri Mar 15 19:16:22 2024 +0100
Add second line to file1.txt
commit 2007711caa39b5ad6bee678519eb0b98e71bd1a3
Author: msdp-book <msdp.book@gmail.com>
Date: Fri Mar 15 19:16:17 2024 +0100
Create file1.txt with the first line
What to notice
Commits from
feature
are visible withgit log --all
, showing the comprehensive history across branches.
Checking out a commit and the detached HEAD State#
We have seen that we can move the HEAD
pointer to a specific branch by using git switch <branch-name>
but we can also move the HEAD
pointer to a specific commit by using git checkout <commit-hash>
. We can also checkout a commit relative to the current position of the branch pointer by using git checkout <branch>~<number>
. This is useful for exploring the history of a repository.
git checkout feature~2
Note: switching to 'feature~2'.
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.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -c with the switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable advice.detachedHead to false
HEAD is now at 4c34d2d Add third line to file1.txt
git status
HEAD detached at 4c34d2d
nothing to commit, working tree clean
git log --all
commit 73aa47900ddb4b5cf6a6fcc222bb6e8f73bc16f4 (feature)
Author: msdp-book <msdp.book@gmail.com>
Date: Fri Mar 15 19:16:33 2024 +0100
Add fifth and sixth lines to file1.txt
commit ae47e9cc31a286967d519a32f7f089776ee1223e
Author: msdp-book <msdp.book@gmail.com>
Date: Fri Mar 15 19:16:31 2024 +0100
Add file2 and modify file1
commit 4c34d2de5a3d346a271ea3c30477f0049ddc88cc (HEAD, main)
Author: msdp-book <msdp.book@gmail.com>
Date: Fri Mar 15 19:16:24 2024 +0100
Add third line to file1.txt
commit d30b4a05bd3582bd7ec537c09ed0c764cc605e1e
Author: msdp-book <msdp.book@gmail.com>
Date: Fri Mar 15 19:16:22 2024 +0100
Add second line to file1.txt
commit 2007711caa39b5ad6bee678519eb0b98e71bd1a3
Author: msdp-book <msdp.book@gmail.com>
Date: Fri Mar 15 19:16:17 2024 +0100
Create file1.txt with the first line
What to notice
We moved the
HEAD
pointer two commits back from the current position of thefeature
branch.By coincidence, the commit we checked out is the same as the commit
main
is pointing.HEAD
is not pointing tomain
is pointing to the commit directly, note that there is no arrow fromHEAD
tomain
, as we used to see before.
When you check out a commit, you are in a detached HEAD state. This means that the HEAD
pointer is no longer pointing to a branch, but to a specific commit. This is useful for inspecting the state of the repository at a specific point in time, but it is not recommended to make changes in this state. If you make changes and commit them, they will be based on the commit you checked out, not on the tip of any branch. This means that if you switch back to a branch, the changes you made in the detached HEAD state will not be part of the branch. To exit a detached HEAD state and retain your changes, you can create a new branch from the current commit:
git switch -c new-branch-name
Switched to a new branch 'new-branch-name'
This command creates and switch to the new branch (in one command) starting from the current commit, effectively bringing your HEAD back to a non-detached state and ensuring that any commits made in the detached HEAD state are not lost.
git status
On branch new-branch-name
nothing to commit, working tree clean
After creating and switching to the new branch we are not in a detached HEAD state anymore, and the changes we made are now part of the new branch.
Deleting a branch#
Finally, once your feature it’s merged, you usually don’t want to keep the feature branch around. To delete a branch, first you need to ensure that you are not currently on the branch you want to delete by switching to another branch or commit. Then use the git branch -d <branch-name>
command. For example, to delete the new-branch-name
branch:
git switch main
git branch -d new-branch-name
Switched to branch 'main'
Deleted branch new-branch-name (was 4c34d2d).
The -d option deletes the branch safely, meaning it will prevent you from deleting the branch that contained unmerged changes. If you are sure you want to delete the branch even if it contains unmerged changes, you can use the -D option, which forces the deletion.