Getting geeky with Git #4. Fast-forward merge and merge strategies

Git

This entry is part 4 of 5 in the Getting geeky with Git

When working with branches, we often need to synchronize our changes. When doing so, we can implement different approaches. In this article, we explain how merging works and discuss various situations. During that, we will touch on the subject of the fast-forward merging and different merge strategies.

The basics of merging

The job of the   command is to integrate a code history that we’ve forked at some point. Let’s look deeper into how it works. First, let’s create a new branch and make some changes.

We see the current state of the branch with  :

commit b53e0718f93eff963181b8c6ef92341641141641 (HEAD -> new-branch)
Author: Marcin Wanago <wanago.marcin@gmail.com>
Date: Sat Jul 18 18:03:06 2020 +0200

Added a line of code

commit cf418d2c640d839570fe3151fc3f12116c118db9 (origin/master, master)
Author: Marcin Wanago <wanago.marcin@gmail.com>
Date: Sat Jul 18 17:52:01 2020 +0200

initial commit

Now, let’s move back to master and merge the changes:

Updating cf418d2..b53e071
Fast-forward
README.md | 1 +
1 file changed, 1 insertion(+)

Fast Forward Merge

One of the most important things about  , when compared to  , is that merging creates a merge commit. This is not always the case, though. Let’s look into the   after the above commit:

commit b53e0718f93eff963181b8c6ef92341641141641 (HEAD -> master, new-branch)
Author: Marcin Wanago <wanago.marcin@gmail.com>
Date: Sat Jul 18 18:03:06 2020 +0200

Added a line of code

commit cf418d2c640d839570fe3151fc3f12116c118db9 (origin/master)
Author: Marcin Wanago <wanago.marcin@gmail.com>
Date: Sat Jul 18 17:52:01 2020 +0200

initial commit

Since our   is very simple, a fast forward merge occurred. It works by combining the histories of both branches. It can happen when there is a linear path from the current branch tip to the target branch. The above is the case since we haven’t committed anything to master before creating the  .

In the third part of this series, we can learn that the branch is a reference.

If we dig a bit deeper, we can see that both   and the   now point to the same commit. This is the case thanks to performing a fast-forward merge.

[b53e071] Added a line of code

[b53e071] Added a line of code

True merge

However, the above is not possible if our branches have diverged. To create such an example, let’s create a new branch but then make some changes to  .

Now that our master includes some new changes let’s go back to .

Now, let’s merge   to  .

Once we do the above, Git fires up the text editor. The default depends on your system. In my case, it is Nano:

The editor opens because the merge results in creating a merge commit. Once we finalize the merge, we get the following:

Merge made by the ‘recursive’ strategy.
feature-b.js | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 feature-b.js

In the above graph, we can observe at which point the   diverges into   and when it comes together with the use of the merge commit.

Let’s look into the merge commit a bit more to understand it better.

tree 6b45e53ad2e6946d3a9f38ccf33a93ff8ef28a28
parent ff435740aad2a498294bd162e611f9a876c2b489
parent 86251d7ea21d1b718e249ca83ae93dbdbb480e48
author Marcin Wanago <wanago.marcin@gmail.com> 1595166982 +0200
committer Marcin Wanago <wanago.marcin@gmail.com> 1595166982 +0200

Merge branch ‘feature-b’

In the second part of this series, we’ve learned that a parent of a commit is simply the previous commit. When we create a merge commit, it has multiple parents. Let’s inspect them:

// …
Added feature A

// …
Added feature B

We can see that the parents of the merge commit are the tips of the branches involved.

Merge strategies

When we attempt to merge two branches, Git tries to find a common base commit. When looking for the latest common commit between two branches, Git can adopt one of a few strategies.

Resolve

It works for merging two branches, and it used to be the default. A detailed explanation of this strategy can be found in Version Control with Git

[…] pick one of the possible merge bases […] and hope for the best. […] Git detects that it’s remerging some changes that are already in place and just skips duplicate changes, avoiding the conflict. Or, if there are slight changes that do cause a conflict, at least the conflicts should be fairly easy for a developer to handle.

Octopus

It is a default strategy when attempting to merge more than two branches. It fails to do so if it encounters situations in which a manual resolution is required. A merge commit created with this strategy has more than two parents.

Recursive

The recursive strategy became the default when pulling or merging two branches in 2005. It has proven to cause fewer conflicts when working on the Linux kernel as opposed to the resolve strategy.

It uses a three-way merge algorithm to recurse over the changes in the branches. The recursive strategy has quite a few options available, such as ours and theirs. For a full list, check out the documentation.

Subtree

It is a form of a recursive strategy. It might prove to be useful when managing multiple projects within a single repository. Github has quite a detailed documentation on this topic with examples.

Ours

When merging, it discards changes from the other branch (or multiple branches), merging just the history. It does not affect the files at all. According to the documentation, it is meant to be used to supersede the old development history of side branches. It differs from using the recursive strategy with the “ours” flag.

Summary

Although it is usually the best idea to rely on Git to choose the best approach to merging, it is useful to have at least a basic understanding of the reasons behind it. When dealing with merges, we might alter our approach slightly to have a cleaner history and fewer conflicts. One of the additional processes that we might want to introduce to our flow is rebasing, and we will cover it in the upcoming parts of this series.

Series Navigation<< Getting geeky with Git #3. The branch is a referenceGetting geeky with Git #5. Improving merge workflow with rebase >>
avatar
  Subscribe  
Notify of