Introduction
In my previous article on the Git version control system, I highlighted ways in which I found Git very helpful. On this page I am documenting where I still have friction in my work with Git.
Author: David Heiko Kolf, 2026-03-16.
My problem with fast-forward merges
In order to understand previous commits or to search for a regression, I would sometimes like to know what the main branch looked like a few years ago. If I would have pulled the main branch in 2019 (for example), what would I have received? With Mercurial and Fossil this is usually an easy task, as the commits on a certain branch carry that branch name forever. When using Git however I still struggle with it.
Other version control systems have the concepts of branches (deliberate strands of diverging commits), forks (multiple heads carrying the same branch name) and synchronizations between multiple clones of the same repository. This can sometimes lead to confusion and Git solved it by uniting the three concepts: there is no difference between a fork and a branch, branches are just names for the different heads and other clones get their own "namespace" for branches (e.g. "origin/main") with which you can synchronize your local branches (just "main").
You would not want to create a merge commit each time you just update your "main" branch to the new state of "origin/main" and so Git offers "fast forward" as the default merge strategy: When the new commit contains the previous commit as an ancestor, the local branch just gets updated to point to the merged branch.
This works fine when updating "main" to "origin/main", but it creates problems when merging feature branches:
In this example "A" is a commit on the main branch. "B" and "C" are commits on a feature branch. Let us assume that "B" is a work step that leaves a feature incomplete — it might even break some functionality that existed on the main branch which only got fixed with commit "C". A fast-forward merge however would turn this into the main branch:
The colors in my illustration are purely for clarity as to which commits were at some point in time referenced by the "main"-head (green). In Git it would no longer be possible to see that "B" was originally on a different branch.
If instead of a fast-forward merge a new merge commit would have been
created using the command option git merge --no-ff, its first
parent would be the previous "main"-commit while its
second parent (the dotted line) would be the commit from the feature branch:
A similar problem can happen when merging other work into the feature branch (in order to test the merge first):
In this example "D" is a recent commit on the main branch. "E" is a merge commit on the feature branch, to make sure that the merge worked before committing it to main. For commit "E" the first parent is "C", while the second parent is "D". If afterwards the "main"-head gets fast-forwarded to the feature-head, the branch history would look swapped when following the first parent of the merge commit:
A merge without fast-forward would have created an additional commit where the first parent keeps pointing to the previous "main"-commit:
This behavior has consequences for tools which display the git history: it is
not possible to cleanly separate between different branches as it would have
been with Mercurial or Fossil. As a consequence Git commits either get
displayed as a list without any relations (the default output of
git log) or the relations quickly turn into an ugly mess of
tangled lines.
Consistently merging different branches without fast-forward would make it
possible to display a clean main history with
git log --first-parent. This would reduce the pressure to use
rebase-and-fast-forward or squash-merges, which have their own drawbacks.
An old story
A well-known guide from 2010 ("A successful Git branching model") already recommended using the --no-ff flag. I still believe it is possible to build tools that can help with the management of Git branches. Unfortunately — besides using the command line with special flags — none are known to me at the moment.
Unverified identities
Git identifies the users with an easily forged plain text containing the name and an e-mail address. It is possible to include additional OpenPGP signatures, but it suffers from the weak fallback: If the signature is missing, users would still look at the unverified text identity.
In my opinion this is solved elegantly by Pijul, where cryptographic signatures are exclusively used as identities.
Only Git?
Git has many strengths and Github pushed it to an almost monopoly where only few people use anything but Git. But just as we are using different programming languages with their individual strengths, I believe it is worth to keep an open eye (and open mind) to other solutions besides Git, to avoid being stuck on a "local optimum" and accepting the drawbacks and workarounds as inevitable.
For projects where many people are expected to collaborate, Git might be the safe default. But for smaller projects I like to experiment with other solutions as well.