People working on laptops

Keep Git History Clean With Rebase Strategy

by Nikita Verkhoshintcev

Recently, I noticed some projects I've worked on didn't enforce strict contribution guidelines.

Most check such things as written unit tests, integration tests, variable names, inner loops, etc. One thing that teams often overlook is the clean git history. It includes requirements for the git commits, e.g., grouping them into logical pieces, having meaningful messages, etc.

Someone can argue that it doesn't affect the functionality and doesn't break anything, so it isn't worth the effort.

Sure, fair enough! However, lousy git history has indirect implications. It makes it hard to review code, find the root cause of bugs, and maintain it in general.

How It Affects A Performance

Here are the most common things I observe in the projects. Those happen regardless of whether it's a small company or an enterprise.

How often have you seen four commits in a row with the same description and minor changes back and forth? It's one of the things that I face most commonly.

A developer adds a line and commits changes. Then reverts it and commits again. As a result, no changes but two commits when there should've been none.

What about poor commit messages? Last year, I worked on an IoT project where most commit messages contained only a dot character. It didn't make sense, and given the fact that the original developer wasn't there anymore, it made it extremely hard for the team to understand.

Another common thing is merging the target branch into a feature branch.

No doubt, I get it. It is easy to do, and many tools do that automatically by pressing the button.

However, let's assume a scenario in which you have a feature branch off the main. You noticed that it was outdated, and there are a ton of commits on the main, including the conflicts. What should you do? Developers often pull changes from the main to the feature branch.

Then, it creates a merge commit containing changes from the main branch. Based on my experience, it can happen repeatedly, even ridiculously many times, within one pull request.

As a result, you have a bloated commit history and changes outside the implementation you worked on.

Is There A Better Way?

The correct way to do it is rebasing instead. I believe you should never merge from the target branch.

Rebasing the branch does what it is supposed to do. It reapplies commits on the top of the latest target.

As a result, you won't get the new merge commits. You will be left with the same commits as you began with, and you can also resolve conflicts and revise the history during the process. You can squash and modify your commits to organize them better and reduce their amount.

A good practice is to keep your pull requests small to make them easier to review. But, sometimes, we eventually have to have large pull requests.

For instance, you change a lot of functionality due to the dependencies. That makes it hard for others to review and understand what's happening.

Organizing the commits into logical format will significantly help because others could review them separately.

The suggested approach should look like the following.

You work on the implementation in the feature branch.

Once you're happy with the outcome and ready to open a PR, rebase to the latest target branch.

git checkout <target_branch>
git pull
git checkout <feature_branch>
git rebase -i <target_branch>

The -i flag means that you want to take the iterative approach and will be able to modify the commits.

By default, each commit has a pick option, meaning it picks the commit as is. Alternatively, you can do squash, fixup, or reword the commits. Here is a list of the available options.

# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label

As the next step, organize the commits into logical groups and edit the commit message to reflect the changes.

For example, the title could be a JIRA ticket followed by a short description of the changes, and on the new line, you can provide a more detailed description of what you have changed there.

Tip: You can create the git pre-hook to take the JIRA issue number from the branch name and prepend to each commit message.

If you need to rework anything, you can always commit changes to the existing commit using the --amend flag.

git commit --amend

That will include changes to the previous commit and allow you to edit the description.

Note: If you rebase or amend the commits, there will be a difference between the local and remote branches.

Once you're happy with the changes, push them to the origin.

Since you will have a difference between local and remote, push your changes using the --force-with-lease flag.

git push --force-with-lease

The flag means it will forcefully override the remote changes unless other developers commit anything. So, you can only override changes done by yourself.

Conclusion

Of course, it requires extra effort. However, it is worth it.

Keeping the git history clean and organized makes you a better developer and benefits the team.

It helps developers review the code, traverse the history to find the root causes for bugs, understand the logic, and maintain the code base.

The cheat sheet with the process is below.

# 1. Create a feature branch.
git switch -c feature/<name>
# 2. Once you finish development, rebase to the latest target,
#    squash the commits, and edit the git history.
git checkout <target>
git pull
git checkout feature/<name>
git rebase -i <target>
# 3. If you need to add more changes.
git commit --amend
# 4. Push your changes to the remote.
git push --force-with-lease
Nikita Verkhoshintcev photo

Nikita Verkhoshintcev

Salesforce Freelance Developer / Solution Architect

I'm a senior freelance Salesforce and full-stack web developer based in Helsinki, Finland. I help companies, consulting agencies, and ISV partners build custom Salesforce applications.

Let's work together!

We help Salesforce customers and SI/ISV partners build custom Salesforce applications. Let's discuss how we can help you!

Contact us