Merging changes
What we have done so far is to create a new feature
branch based on main
, switch to the feature
branch, make some new changes and commit them.
Now we want to switch back to the main
branch and add some new changes there.
Exercise: Switch to the main
branch and make some changes
- Switch to the
main
branch.- Add
D
andE
to theREADME.md
file the same way as we just addedB
andC
to thedocument.md
file.
Command reminder:
git switch <branch-name> # to switch to a branch
git status # to see the status of the working directory
git add <file-name> # to add a file to the staging area
git commit -m "<message>" # to commit the changes
Solution: Switch to the main
branch and make some changes
git switch main
echo "D" >> README.md
git add README.md
git commit -m "D"
echo "E" >> README.md
git add README.md
git commit -m "E"
If we run git log
we should see something like this:
commit 1fef21501625d1cb1ab99318ad0fa8487d6ef5cb (HEAD -> main)
Author: Lasse Lund Sten Jensen <lajl@itu.dk>
Date: Fri Sep 20 22:27:19 2024 +0200
E
commit 8ae69da5d2938a5fee06207f2c3320a4c5b90a9b
Author: Lasse Lund Sten Jensen <lajl@itu.dk>
Date: Fri Sep 20 22:27:06 2024 +0200
D
commit 16b8f7f933c4c92b83ebb8602109c84c2d799359
Author: Lasse Lund Sten Jensen <lajl@itu.dk>
Date: Fri Sep 20 21:48:09 2024 +0200
A
We can see that D
, E
, and A
are all on the main
branch.
The branches now look like this:
This means the branches have diverged.
They have commits that are unique in both and they have a common ancestor A
.
Git merge
When we have two branches that have diverged, we can merge them back together.
When merging we can have two different outcomes—one with a merge commit and one without. Strategy for merging matters here.
What is a merge exactly?
A merge is a way to combine the changes from one branch into another branch. Essentially combining two histories together that have diverged somewhere in the past.
As in our example, where main
and feature
have diverged from a shared commit A
. Here A
is the first common ancestor. This is also called the merge base.
When we merge, git
- walks the history of the two branches,
- finds the merge base,
- checks out the merge base
- and "plays" those commits on top of it,
- and then creates a new commit with the changes from both branches called the merge commit.
A merge commit has two parents. One from the branch we are merging from and one from the branch we are merging to.
Merge
We have the merge
command.
The branch we are on (i.e. has checked out, with the *
) is the branch we are merging into, also called the target
branch. The branch in <branch-name>
will be the source
branch.
Command
git merge <branch-name>
Read more about it in the manual:
man git-merge
To merge the feature
branch into the main
branch, we can use the merge
command.
However, since we will be using this to demonstrate another merge technique, let us first checkout a copy of the main
branch, so we do not mess up our history.
We call it main-merge-feature
.
git checkout -b main-merge-feature
Exercise: Merge the feature
branch into the main-merge-feature
branch using the merge
command
- Switch to the
main-merge-feature
branch, if you are not already on it.- Merge the
feature
branch into themain-merge-feature
branch.- Check the log to see the result.
- (Extra) Use the
--parents
flag to see the parents of the merge commit.
Command reminder:
git switch <branch-name> # to switch to a branch
git merge <branch-name> # to merge a branch into the current branch
git log # to see the logs
Solution: Merge the feature
branch into the main-merge-feature
branch using the merge
command
git switch main-merge-feature # switch to the target branch
git merge feature # merge source branch into the target branch
We will be presented with this message.
Output:
Merge branch 'feature' into main-merge-feature
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
This is the default message when merging. We can just save and close the editor by typing :wq
and pressing Enter
.
Note:
:wq
is the command (:) to write (w) and quit (q) in vim—usually the default editor in git.
We should see something like this.
Output:
Merge made by the 'ort' strategy.
document.md | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 document.md
Next, we can check the log to see the result.
git log --oneline --graph --all
Output:
* 8d3108b (HEAD -> main-merge-feature) Merge branch 'feature' into main-merge-feature
|\
| * 312c2e9 (feature) C
| * a582ce0 B
* | 1fef215 (main) E
* | 8ae69da D
|/
* 16b8f7f A
We can see that the main-merge-feature
branch now has the commits from both main
and feature
. The branches have been merged together.
Extra: Using the --parents
flag
Taking a look at the log with the --parents
flag, we can see the parents of all the commits.
git log --oneline --graph --all --parents
Output:
* 8d3108b 1fef215 312c2e9 (HEAD -> main-merge-feature) Merge branch 'feature' into main-merge-feature
|\
| * 312c2e9 a582ce0 (feature) C
| * a582ce0 16b8f7f B
* | 1fef215 8ae69da (main) E
* | 8ae69da 16b8f7f D
|/
* 16b8f7f A
Fast forward merges
When we have a branch that has diverged from another branch, but the branch we are merging into has not changed since the divergence, we can do a fast forward merge.
Exercise: Merging a branch into another branch using a fast forward merge
- Switch to the
main
branch.- Create a new branch
feature-two
and switch to it.- Create a new document
document-two.md
.- Add values
X
andY
to thedocument-two.md
.- Add two commits with messages
X
andY
.- Merge the
feature-two
branch into themain
branch.
The branch structure should look like this.
Solution: Merging a branch into another branch using a fast forward merge
git switch main # switch to the main branch
git checkout -b feature-two # create and switch to the feature-two branch
# add X change
echo "X" >> document-two.md
git add document-two.md
git commit -m "X"
# add Y change
echo "Y" >> document-two.md
git add document-two.md
git commit -m "Y"
git switch main # switch back to the main branch
git merge feature-two # merge the feature-two branch into the main branch
Output:
Updating 1fef215..562f958
Fast-forward
document-two.md | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 document-two.md
Observation
The fast-forward merge happened because the common ancestor of the main
and feature-two
branches was the tip of the main
branch itself.
Cleanup
If you followed the exercises up unti now, you can delete the
merge-feature-two
and thefeature-two
branches.Use
git branch -d <branch-name>
to delete a branch.
Git rebase
Assume we look at the git history and we have the following branches: main
and feature
.
They should look like this.
By rebasing, we can make the history look like this.
Rebasing is the act of taking the changes from one branch and apply them on top of another branch. Or to the tip, really.
Essentially "rewriting" git history.
This can be useful if we want to keep a clean history, without merge commits. It allows us to apply our changes on top of the latest "reality".
How rebase works
Basically, when rebasing this is the flow.
- Execute
git rebase <target-branch>
. The branch you are on when running the command is the branch you are rebasing—we call it<current-branch>
. - The
<target-branch>
is checked out. - The commits from
<current-branch>
are "played" one commit at a time. - When done, the
<current-branch>
is updated to the latest commit SHA of<target-branch>
which now has all the changes from<target-branch>
as the base.
Important
It will lead to complications at some point if you do not understand this flow and what goes on.
Exercise: Rebase the feature
branch with the main
branch
In order to not mess up the history for other exercises, we will create a new branch feature-rebase-main
and rebase that branch with the main
branch.
Do this first.
git checkout feature
git checkout -b feature-rebase-main
- Switch to the
feature-rebase-main
branch.- Rebase the
feature-rebase-main
branch with themain
branch.- Check the logs to see the result.
Command reminder:
git rebase <target-branch> # to rebase a branch with the current branch
git log # to see the logs
Solution: Rebase the feature
branch with the main
branch
git checkout feature-rebase-main # switch to the current branch
git rebase main # rebase the current branch with the target branch
Check the logs to see the result.
git log --oneline --graph --parents
Output:
* 109ad4e b12a712 (HEAD -> feature-rebase-main) C
* b12a712 562f958 B
* 562f958 37fb152 (main) Y
* 37fb152 1fef215 X
* 1fef215 8ae69da E
* 8ae69da 16b8f7f D
* 16b8f7f A
Observation
Rebase rewrites the history of the current branch with the missing commits from the target branch. When this is done, rebase actually creates new commits with the same changes, which is why you will see different commit SHAs.
Read more about merging vs. rebasing. Especially the section "The golden rule of rebasing":
Pros
- Clean history, no merge commits.
Cons
- Alters history of a branch. Rewriting history can be dangerous.
- If a branch was already on the remote, we need to force push the changes. This can lead to complications if others are working on the same branch.
Warning
- Never rebase a branch that is shared with others.
- NEVER CHANGE HISTORY OF A PUBLIC BRANCH—Meaning, do not change the history of
main
or whatever branch you are using as the main branch. - You will end up with two branches with the same name, but with different histories, which will lead to merge conflicts and team frustations. The moment you do this, it breaks everybody.
Rule of thumb here
- Always merge on public branches.
- Only rebase on private branches.
There is a lot more to learn here, but for the scope of this course, this is enough.