Introduction to Merge Modes
After reviewers have reviewed the code changes submitted by developers, they can decide whether to merge these commits into the main branch master
.
However, there are multiple merge modes for merging commits between different branches. The image below shows the merge modes supported by GitLink, including Merge Pull Request, Rebase and Merge, Rebase and Merge --no-ff, and Squash and Merge.
- Merge Pull Request
Merge Pull Request is the most commonly used merge mode. Taking the following diagram as an example, the developer created a development branch dev
from commit 3 of the main branch master
, then made commits A, B, and C, and then merged back to the master
branch.
Before fast-forward merge:
After fast-forward merge:
Note: As you can see, the merge process simply moves the master
pointer to where the dev
pointer is. This type of merge is called fast-forward. This occurs because after commit 3, there were no new commits on the master
branch, so the merge can be completed by simply fast-forwarding the master
pointer. However, if there are new commits on the master
branch, a substantive merge will be needed, as shown in the following two images:
Before merging, commit 4 was made on the master
branch after commit A but before commit B on the dev
branch. In this case, merging the dev
branch cannot be done by simple fast-forward movement; instead, the changes on both branches need to be compared and merged.
Before non-fast-forward merge:
After merging, commits A, B, and C will be added to master
's commit history according to the timeline, and a new commit D will be generated to record the merge event. Additionally, if conflicts occur during the merge (i.e., both branches modified the same file), they need to be resolved manually. This merge method is called non-fast-forward, and it's the default method in Merge Pull Request mode!
After non-fast-forward merge:
For better understanding, you can view the commit history on the master
branch linearly:
Summary: In Merge Pull Request mode, non-fast-forward merge is used by default to merge development branches into the master
branch, and the non-fast-forward method generates a special commit to record this merge event!
- Rebase and Merge
From the commit history on the master
branch after Merge Pull Request, we can see that the commit histories of the two branches might interweave, which could cause confusion in subsequent development. Rebase and Merge can solve this problem.
Rebase and Merge includes two operations: rebase and merge. First is rebase: taking the diagram below as an example, the dev
branch was created from commit 3, so commit 3 is the base of dev
. The rebase operation changes the base of dev
to the latest commit on the master
branch. Of course, conflicts may occur during rebasing and need to be resolved manually.
Before rebase:
After rebase, before merge:
After the dev
branch is rebased, there are no "newer" commits on the master
branch, so performing a merge at this point gives us the following result:
After merge:
Summary: In Rebase and Merge mode, the development branch dev
can first perform a rebase operation, making its commits appear as if they were made based on the latest commit of the master
branch, then merge back to the master
branch using fast-forward, thus organizing the commit history!
- Rebase and Merge --no-ff
Because Rebase and Merge uses fast-forward mode by default when merging, there won't be a special commit on the master
branch to record this merge event. Therefore, you can use the --no-ff
(no fast-forward) option to specify using non-fast-forward mode for merging.
Before --no-ff merge:
After --no-ff merge:
Summary: Using the --no-ff
option explicitly declares using non-fast-forward mode when merging, which adds a commit to record the merge event in the master
branch!
- Squash and Merge
In development branches like dev
or feature
, developers might make multiple commits to complete a requirement. However, these granular commit messages can make the commit history on master
cluttered and confusing after merging. Therefore, these commits need to be squashed before merging. As shown in the image, the squash operation is performed on the master
branch. Essentially, it applies the changes made on the dev
branch to the files maintained by the master
branch, then saves these modifications with a new commit 5, and finally commits.
Before squash:
After squash, before commit:
After commit:
Summary: Squashing the granular commits on the development branch before merging can make the commit history on the master
branch more concise. However, note that this merge mode essentially saves the changes from dev
to master
in one go and creates a new commit to record these changes, so the committer changes!