Consider a feature branch which has conflicts against the mainline branch. The developer merges from upstream in order to bring in those changes and resolve the conflicts; the branch is then pushed to the server, where the web interface offers a "merge" button for this branch.
If the mainline branch has not moved since the developer merged it into the feature branch, the server has two options to perform the merge: it can create a new commit on top of the mainlie branch in which it merges the feature branch into the mainline branch, or it can perform a fast-forward "merge", making the mainline branch point to the same commit as the feature branch.
These are the two modes of operation of the git merge
command, and
it's what is generally expected when a program or system implements
merge functionality in git. However, the second option can cause
issues in the scenario mentioned above.
The history fo the parents is significant during a merge. The first parent is the commit into which changes are being introduced. The second parent is the commit being brought in as a merge. So the Git system knows which direction merges went in. In this case we have a merge from the mainline into the feature branch.
By making the tip of the feature branch now also become the tip of the mainline branch, we would make this feature branch in essence the main branch. This means that the first parent of this commit is the tip of the feature branch, and the incoming changes are from the mainline branch. This also means that all those commits which were made on the mainline branch between the branch point and the fast-forward "merge" will now look like they've been done in a side branch (interestingly in one which bears the name of the mainline branch).
In the scenario above, after merging into the feature branch, the branches look like
-----x----x----x mainline \ \ x--x---x--x feature
and if we perform a full merge, the history becomes
-----x----x----x---x mainline \ \ / x--x---x--x feature
and looking at the log from the mainline's point of view everything is
as expected. If we however perform a fast-foward (what git merge
would do by default) we get something different
------x--x---x---x mainline, feature \ / x---x--x
By making the tip of the feature branch become the tip of the mainline, those three commits we saw in the mainline have now become part of a side of the history, and those commits building up the feature branch now form part of the mainline history.
A look though git log
would usually show the same commits, as it
orders primarily by time so you would only miss the extra commit made
by the no-ff variant. However, if you look at git log --graph
,
you'll see that the mainline branch now has those small commits from
the feature branch, instead of bringing in those changes.
If you use git log --first-parent
to filter out side history and
figure out what was done directly on the mainline (e.g. what was
merged, what was a hotfix made directly there), you'll now see those
commits from the feature branch as part of mainline's direct history,
and it will hide those commits which were in fact made directly on the
mainline and would have shown up as such before the fast-forward
"merge".