Tomáš Repčík - 16. 6. 2024

Git Squash for Project Planners: Simplifying Your Commit History

Why I Prefer Squashing of the Branches

Recently, I was challenged with the question:

Why do I have squashing of branches turned on by default during merge request?

It has been my habit for a long time and caught me off guard.

Consequently, my first answer could have been more specific, because it simplifies the history or you do not need to see every step of your development.

I got back, that I will probably lose some history/context, it is perhaps not the best practice and I should be more aware of that.

The stage for another rabbit hole was created and I had to think it through once again, why I started it.

Squashing

For those who do not know what is the squashing:

Git Squash merges multiple following commits into one. It is primarily used for cleaning up git history of redundant commits or aggregating changes. You can define new commit message on top of new squashed commit.

Example

Let’s put an example for TODO mobile app with one screen, where there is a list of items pulled from the local database. Minimalistic idea as much as possible. We should not be bothered now by some cloud synchronization of how the items are created or what is inside of them.

Squashing and developers

Squashing is causing problems

Unfortunately, some developers will squash many unrelated changes into one commit.

For example during the implementation of the view model, a new version of the database is released and it brings in breaking changes.

Someone might implement those changes with changes of the view model. There could be separate commits for that:

But after merging, someone could just name it: “adds viewmodel implementation to handle database events” and that is it. No note of the version change.

Afterwards, some QA or developer might find out that the database has unexpected behaviour, but they do not need to be aware that the version of the database has changed. This leads to extended debugging of the issue.

Why squash

Developers use squashing usually to clean up their work in terms of getting rid of partial progress, fixing their errors, exploration steps and others.

In the end, they remove the steps which do not bring the value.

That is one of the correct ways of using squash.

Some developers are doing it before merge/pull request, or during the development. It is more intentional, but sometimes you forget to do it. Afterwards, you have to deal with complex history or irrelevant changes.

The main benefit is clearer, less complex history which can be browsed faster.

How do I do it - the importance of planning

Planning is the essence of everything. It does not require much, but before you jump into coding something, stop for a while and think of a couple of steps ahead. For such TODO app screen, the plan might look like this:

In terms of commits (from the git perspective it would be in reserve):

In other words, every achievement is worth committing and merging, if it works and does not break the code base.

This is just one way of doing it, but it depends on the project, methodology and requirements. Granularity depends on the scope of your project.

Anyway, it gives us a good representation of what was happening during the development. Every merge request could be merged gradually into the feature branch.

If it took me 2 commits or 30 commits to introduce the create method for the database, it is up to me and my coding style. Afterwards, the changes are squashed and I am left with one definitive message what was the goal.

How it was done, should be understood from readable code underneath the commit.

The most important fact is that every commit should only introduce one change, which the commit message describes. Nothing more.

However, we do not live in the ideal world and many things can change during the development or urgent fix is required, but it does not matter. With this approach, it is not a problem, because it mirrors gradual changes in the codebase.

The result will be still a cleaner history without any noisy commits, which are the product of developer struggling to bring the final result.

Moreover, you do not have to think about the commits which should be squashed and it is done automatically by the hosting git repository service as GitHub, GitLab or Azure DevOps.

What to do with big features or changes?

Create yourself a feature branch into which you will merge gradual steps, which are squashed. Did you achieve meaningful achievement? Merge it into your feature branch. 

You can do this step-by-step approach as usual, but instead of merging right into the master, merge it into your feature branch.

Afterwards, merge the feature branch into the master, but without squashing, so you can see how the big change was introduced.

Couple of Dos

Final words

In the end, it is up to you, your team and the project. Every team has its own needs and the frequency of squashes must be determined by the needs of the team.

I found that for me this works quite well most of the time because I do not have to think about it before every merge request and I can just do my work in accordance to the created small plan beforehand.

Thanks for reading!

Subscribe for more
LinkedIn GitHub Medium Threads X Bluesky