Question

How to squash all merges that only merged a single commit (due to git pull)?

In a git repository with several contributors, there are a lot of commits titled Merge branch 'main' of <repository url>. These create unnecessary clutter when looking through a git log. Most of them are created by one person doing work on a branch while another person has pushed to the same branch in the meantime. The first person commits then pulls, creating the merge commit.

We could try and require everyone to use git pull --rebase, but that wouldn't affect past commits and would make it harder for some of the contributors who are newer to Git. All these merges have only one commit of divergence before them, so they are straightforward to take care of manually using git rebase -i, but that's a lot of work. Is there a way to remove all these merges (squashing them into the previous commit) without having to manually squash them one by one?

 2  57  2
1 Jan 1970

Solution

 1

Disclaimer: rewriting a shared branch is generally frowned upon. If you do it, you'll likely need to pause all development, make your change, and then provide instructions to all users of the repository on how to rebase all of their local branches onto the new version of the shared branch.

That being said, if none of those mini merges had merge conflicts, then you were so close:

All these merges have only one commit of divergence before them, so they are straightforward to take care of manually using git rebase -i, but that's a lot of work.

If you're willing to do that, then all you need to do is drop the interactive portion which leaves:

git rebase

This works because by default rebase drops all the merge commits to make a linear graph. So your actual command set might look something like this:

# update your local copy of origin/main
git fetch
# blow away your local copy of main and replace it with the remote version
git switch -C main origin/main
# rebase the entire main branch onto itself starting from the beginning
git rebase --root

# After coordinating with all other users:
git push --force-with-lease

Side Note: this isn't actually "squashing" the merges, but instead is doing what you probably want which is simply removing the merge commits. If you were in a situation where you had more than 1 commit per merge, and for some reason you wanted to squash all of those commits with their merge commit into a single commit per merge, then that would require a different solution. I wouldn't actually recommend doing that though, since you could see the identical view with git log --first-parent without rewriting history at all.

Tip: while you're telling everyone how to rebase their in-progress branches, you might as well have them configure pull to rebase like you suggested.

2024-07-19
TTT

Solution

 0

Basically I would push back against the entire premise of the question. If you anticipate a possible conflict when your feature branch is merged into main, or if you need to take on board recent changes that you know are now in main but were not when you started your feature branch, or simply if your feature branch lives longer than day or two, merging main into your branch is correct. You should not be objecting to this or contemplating a different workflow.

These create unnecessary clutter when looking through a git log

That depends on how you "look through the log". As you've already been told, saying git log --first-parent eliminates the path through the merge. And there are many other git log tweaks that let you simplify the history (see the section in the docs about history simplication). In other words, I suggests that this "clutter" is because you're not taking advantage of the ways that Git lets you examine the history, not because there is something wrong with the topology of the history.

2024-07-19
matt