Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions content/blog/2020-04-24-rebase-vs-merge.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ which situations they are an advantage to use.
Rebase gives you the opportunity to move the base of a branch to a new
base. Here we have two branches, `master` and `feature_A`.

![Initial tree](/blog/image_01.png "Initial git commit tree"){:class="img-responsive" style="max-width:100%"}
![Initial tree](/blog/image_01.png "Initial git commit tree")

We can rebase the `feature_A` branch to the last commit in `master`:

Expand All @@ -31,17 +31,17 @@ git rebase master
The result will look like this.

![First rebase tree](/blog/image_02.png "git commit tree
after rebase"){:class="img-responsive" style="max-width:100%"}
after rebase")

Checking out `master` again, we can merge `feature_A` with `master`. The merge will by default be a fast-forward. We end up with a linear history, which many find attractive as it is easy to follow. The disadvantage is that we rewrite the history as the commit hashes changes.

![FF-merge tree](/blog/image_03.png "git commit tree after fast-forward merge"){:class="img-responsive" style="max-width:100%"}
![FF-merge tree](/blog/image_03.png "git commit tree after fast-forward merge")

### Merge

If we don’t use rebase, but just merge `feature_A` with `master`, we get an merge commit, a new commit pointing to the previous last commit in `master` and the previous last commit in `feature_A`.

![Plain merge tree](/blog/image_04.png "git commit tree after regular merge"){:class="img-responsive" style="max-width:100%"}
![Plain merge tree](/blog/image_04.png "git commit tree after regular merge")

If we only do merges, we show the true story of the repository, how the code came to be. As the repository grows with new branches, maybe more contributors, following the history can become very challenging. The git graph can look like the tracks of a large railway station, where it can be hard to find the ancestors of a feature.

Expand All @@ -59,7 +59,7 @@ How will this look?

Let us say we have created a new function or class, something that belongs together - a semantic unit we call `feature_B`. The base of `feature_B` is the last commit in `master`.

![Master feature-b tree](/blog/image_05.png "git commit tree with master and a new feature"){:class="img-responsive" style="max-width:70%"}
![Master feature-b tree](/blog/image_05.png "git commit tree with master and a new feature")

If we do a merge, git will by default do a fast-forward merge. Following our newly stated policy, we want this merge to be a merge commit. Consequently, we add the option --no-ff to the merge command:
```sh
Expand All @@ -74,13 +74,13 @@ git config --global branch.master.mergeoptions --no-ff

The result will be like this, where the feature is clearly visible in a feature path, presumably with well written commit messages explaining what has been added in this path of work.

![No-ff merge tree](/blog/image_06.png "git commit tree after --no-ff merge"){:class="img-responsive" style="max-width:100%"}
![No-ff merge tree](/blog/image_06.png "git commit tree after --no-ff merge")

#### Rebase revisited

Now we take the case where we checkout a branch from C1 to do some corrections. While we were doing the corrections, at least before we were able to complete the corrections, `master` moved to M1 as in the picture above. A merge commit will add unnecessary complexity to the story of our project. We are not adding a new semantic unit, just fixing things that got wrong in the first phase. That we started to fix things from C1 is not necessarily a important information to keep for the project.

![No-ff merge tree plus patch](/blog/image_07.png "git commit tree with a merged feature branch and a patch branch in master"){:class="img-responsive" style="max-width:100%"}
![No-ff merge tree plus patch](/blog/image_07.png "git commit tree with a merged feature branch and a patch branch in master")

Following our second principle, we rebase the fix_typos branch to M1. Then we do a merge, but this time as fast-forward. If we have configured merge for not doing fast forward when possible, as the configuration statement above, we need to add the --ff-only argument:
```sh
Expand All @@ -91,7 +91,7 @@ git merge fix_typos --ff-only
```
The git graph will now look like this:

![No-ff merge plus rebase](/blog/image_08.png "git commit tree with a merged feature branch and a rebased patch branch"){:class="img-responsive" style="max-width:100%"}
![No-ff merge plus rebase](/blog/image_08.png "git commit tree with a merged feature branch and a rebased patch branch")

### Rebase vs merge revisited
Rebase and merge serve two different purposes. We can use this to our advantage to create a clear story, a more readable git log (It is important to create a story, remember?). By using the above principles as guidance, we will become more conscious of where these operations will serve us or add more clutter. For instance, we might conclude that rebasing semantic branches, but insisting on a merge commit, is perfectly fine, because it is where the feature (the semantic entity) enters the `master` branch which is important, not where the development first started. Features will clearly stand out as a visible pattern in a git repository following such a practice.
Expand Down