Problems I have with Git (dkolf.de)

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:

Illustration of the feature branch described in the following text.

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:

Illustration of a fast-forward merge, with all commits being on the line of 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:

Illustration of a feature branch being merged using a merge commit.

A similar problem can happen when merging other work into the feature branch (in order to test the merge first):

Illustration of a feature branch having a merge to update it to the latest main state.

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:

Illustration of a fast-forward merge on the update-merge commit.

A merge without fast-forward would have created an additional commit where the first parent keeps pointing to the previous "main"-commit:

Illustration of a second merge commit to bring the feature branch back on the main branch.

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.