2 Comments

Four Steps To Maintaining a Clean Git History

Git is a very important tool. Not only does it keep a history of a project, but Git also makes it easy for a team to collaborate in a codebase. Although it’s such an important tool, it’s often under-utilized and on occasion, even neglected.

A clean Git history is easy to understand and tells a story about the project. It’s evident when features were added and how they were implemented. I’ve come to cherish a clean Git history on a project. The good news is, it’s not very difficult to keep this history clean.

1. Always Work on a Branch

Working on branches has a couple of advantages. First, when a developer is working on a branch, he or she is not polluting the main branch with work-in-progress commits. Secondly, branches make it easy for multiple people to work on a feature set. Lastly, branches are a vital element in condensing all of the work on a feature into a few logical commits… but more on that later.

2. Avoid Merge Commits with Rebase

Your Git history provides great documentation for a project. But it can be impossible to read a project’s history when it is littered with merge commits. That is where rebase comes in.

There are two situations where it’s important to use rebase. The first is when two developers are working on a branch. If Dev 1 pushes a change, Dev 2 is unable to push his or her changes until he or she runs git pull. If Dev 2 simply runs git pull, a merge commit will result. Instead, Dev 2 should run git pull --rebase. This will write Dev 2’s commits on top of the commits made by Dev 1, and importantly, it will not result in a merge commit.

The second situation is when a developer is ready to bring branch work onto the main branch. A few common commits on my branches are: “WIP,” “Run Rubocop,” and “Fix typo.” There is no reason these commits should be remembered in the history books of a project. Instead, everyone should run git rebase -i <parent-branch>. This brings up a list of all commits on the branch with a default option to “pick” the commit.

There are a few more options, such as rename, remove, and most importantly squash. Squash will combine multiple commits into a single commit. That’ll get rid of those pesky WIP commits.

3. Merge and Squash

The main branch of a project should be a summary at the highest level. It should be trivial to locate when and how features were implemented. Discipline with squashing on a branch is a big help, but to completely roll up a feature, it should be squashed again when it’s merged with git merge --squash. This will roll all commits into a single one before merging. It’s nice because the single commit message will contain all of the other commit messages.

4. Delete the Branch (Locally and Remotely)

Keeping branches around longer than necessary creates confusion. Was that branch already merged? Did it still have more work to be done? Was it a spike that never should be merged?

To prevent this confusion, as soon as a branch has served its purpose, delete it. There is an awesome Git extension project that has a shortcut for deleting a branch locally and remotely. It’s as simple as running git delete-branch <branch>.

Keeping a clean Git history takes some discipline. But when these steps are followed, it’s easy to read and understand the history of a project. What techniques do you use to keep your project’s history clean?