Categories
Best Practices

Messed up your git history? Use git merge squash to clean it.

Introduction

Have you ever found yourself in a situation when you made a lot of commits with messages like fix, more fixes in the dev branch? I did. It could have been that you stayed truthful to keeping git commits as small as possible. However, this rule applies only if your small commits were fully complete and tested. Let’s be honest, such commits most probably weren’t. Apparently, you just wanted to finish this never-ending task just to get it done.

Eventually, if you are lucky and patient enough, all these fixes lead to some working code. Would you merge it master? You could, but all these commits won’t pass code review. They shouldn’t, as this would make main branch messy as well. No one will be willing to understand these commits. Moreover, no one should ever revert to these commits, because they don’t represent a stable state in a repository history. So, what should we do?

Luckily, git merge --squash is the magic command which will help us to “squash” all these commits into one. The one we will merge to main branch

While part 2 of Introduction into Kafka in Production is still in the works, I’d like to share how helpful git merge --squash might be when we messed up our git history.

To get some context, click here to see a visual demo of how git merge --squash works and read its documentation here.

Keep reading if you’d like to give it a try on a sample repository.

Firstly, we’ll simulate messing up our git history. Then, we’ll apply git merge --squash to clean up the mess.

Let’s mess up our git history

  • clone sample repo from my github and cd into it. 
  • we are on main branch:
$ git status
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
  • let’s create and checkout new dev branch:
$ git checkout -b dev
  • Run below script to mess up our git history. It will append character a to file a inside the repository 10 times and commit each change. 

./append_and_commit.sh

  • let’s inspect the mess we created:
$ git log -n 10 --pretty=oneline
e8e0014f06eb7497079f8ab43b63cf86cd0b304a (HEAD -> dev) fix
1c001704ab177ac0fd3cc336756a8282c8379741 fix
2f1bce48d4569c606eb12d88ae5e74066727c048 fix
67094f12d25c1789f0952ee483bb0722c16258a1 fix
...
  • resist the temptation to push these messy 10 commits to remote and merge to master. 

Git merge squash it

We are just 4 steps from salvation. What would save us? Right, squashing all 10 commits into one with a meaningful commit message which we’ll merge to master. 

  • let’s go back to main branch:
$ git checkout main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
  • Get changes merged to main branch by other developers while we were doing our numerous fixes. Of course, this is not needed in our demo. However, doing so in real life might save you from resolving merge conflicts when you merge your remote dev branch to remote main. 
$ git pull
Already up to date.
  • create new_dev branch from main:
$ git checkout -b new_dev
Switched to a new branch 'new_dev'
  • Show time:) Merge dev branch into new_dev branch. Note that merge --squash is done using fast forward by default. 
$ git merge --squash dev
Updating e65d8ba..e8e0014
Fast-forward
Squash commit -- not updating HEAD
a | 10 ++++++++++
1 file changed, 10 insertions(+)
  • Did we merge all those messy commits? Of course not! 
git log -n 1 --pretty=oneline  
e65d8ba0540c8496606e61452634e56c847c8a80 (HEAD -> new_dev, origin/main, origin/HEAD, main) added append and commit script
  • Did we merge performed changes to a file? Yes, of course:
$ git status
On branch new_dev
Changes to be committed:
 (use "git reset HEAD <file>..." to unstage)

       modified:   a
  • let’s commit this change with a meaningful commit message:
$ git commit -am "many many fixes, now it works, really"
[new_dev 8878345] many many fixes, now it works, really
1 file changed, 10 insertions(+)
  • push changes to remote:
$ git push --set-upstream origin new_dev
...
To github.com:w7089/git_squash_example.git
* [new branch]      new_dev -> new_dev
...
  • Now it’s safe to open pull request and merge to main. Do it on your github if you forked my sample repository.
  • Let’s go back to main branch and pull merged changes from remote main:
$ git checkout main             
Switched to branch 'main'
Your branch is up to date with 'origin/main'.


$ git pull
...
From github.com:w7089/git_squash_example
  e65d8ba..a60e3c4  main       -> origin/main
Updating e65d8ba..a60e3c4
Fast-forward
a | 10 ++++++++++
1 file changed, 10 insertions(+)
  • Let’s inspect main branch history and notice our single commit.
$ git log -n 3 --pretty=oneline
a60e3c49793d7d9c017c32d2e93efef89ee05157 (HEAD -> main, origin/main, origin/HEAD) Merge pull request #1 from w7089/new_dev
88783458b2cd0beffd82cb65e43178d02f93e5ab (origin/new_dev, new_dev) many many fixes
e65d8ba0540c8496606e61452634e56c847c8a80 added append and commit script

Final words

Wow, this post turned out to be longer than I thought. And we discussed just one git command.

git merge --squash has been really a rare jam waiting to be discovered in my journey of git usage 🙂 Without a doubt, it deserved its own dedicated post.

Feel free to share. If you found this article useful, take a look at the disclaimer for information on how to thank me.

You may find below articles useful as well:

Bonus: Recommended Git courses on Pluralsight I learned from.

Sign up using this link to get exclusive discounts like 50% off your first month or 15% off an annual subscription)