Keeping a clean git history can save a lot of time when trying to track down commits related to a bug or issue that is disrupting dev efforts. GitHub provides three options when merging in commits, these three options being: – Create a Merge Commit – Squash and Merge – Rebase and Merge Merging with a merge commit, squash merging, and “Rebase & Merge” should be pretty familiar as these are commands that are already commonly used when working on dev branches to keep commits on PRs tidy. We can apply this way of thinking when we want to keep the master branch Git history clean and helpful to future you and other developers who may be combing through the history to figure out why the code structure is the way it is.
Create a Merge Commit
The default option
Merge pull request on Github will take all commits from the pull request and add them to the master branch with a new commit in a merge commit.
So for example, we have a base master branch with an initial commit and 2 new commits, the new commits are
To work on a new feature, we’ll branch off of master branch and create a new branch called
feature-branch. After doing some work, we’ll have 2 additional commits
Four in which we want to merge into master.
When using Merge pull request, (assuming that nothing has been merged into master previously) commits 3 and 4 will be added to the master branch as a commit, which we will call Merge branch feature-branch. This will be the new HEAD.
Merge branch feature-branch on the repo Git history will look like
Merge branch feature-branch. The hyperlink provided by the message will take you back to the original PR with commits
While this does merge in the PR into the master branch, the resulting git message isn’t very helpful when grepping through git commit messages to figure out what exactly changed when we merged the feature-branch. At Rietta, we have disabled the default “Merge pull request” in favor of ether squash merging or rebasing pull requests. You can also modify this in your repository settings.
Squash and Merge
Squashing works much like rebasing a branch, you can take a 5 commit pull request and squash it into a single commit. This may seem similar to Merge pull request, but is subtly different.
Taking the same scenario as before:
Merge pull request is simply adding the feature-branch commits with commit
52906c3 Merge branch feature-branch and is using it as a reference to the PR with both of the feature-branch commits. Instead of adding commits
4 with commit
Merge branch feature-branch, we are combining
4 and the resulting combined commit is
3(squashed)(I explicitly named it
3(squashed), you can name it whatever you like!).
The squash option on the Github UI will allow you to change default commit message which will be. The following commits will be squashed under the default commit message and look like this:
But if you use it… don’t forget to edit the message! The default github squash merge is poorly formatted and pretty ugly.
1 2 3 4
We like to use this option when a feature branch has a plethora of smaller commits that are added to keep track of work being done to a feature branch that don’t necessarily need to have their own commit, we can instead compress them into one commit. The commit can also have a descriptive commit message with additional important information nested below it.
Rebase and Merge
So while squash merging is useful, what about hotfixes or one off commits that can be merged in without using Merge pull request or squashed down, when dealing with a small commit with specific code changes?
This is where rebase shines as a way to keep the Git history clean for hotfixes or one off commits. We can look at the same scenario and see how rebasing it can keep the merge process simple and the Git history clean.
The feature-branch has master as the base and we are introducing commits 3 and 4. Rebasing takes our feature-branch and transplants its commits on top of the master branch. We are changing the HEAD of the master branch to be commits 3 and 4, there is no Merge branch feature-branch to add commits 3 and 4 like we see using Merge pull request.
The resulting Git history has have the commit message and SHA present like it was on the feature-branch, this time it will be the HEAD of the master branch.
There are many ways we can keep our Git commits clean on PRs to assist our code reviewer in navigating through our changes.
The same mindset can be applied when maintaining a project that has tens of thousands of commits. For example, take the official Ruby on Rails repository! It has over 73,000+ commits and is continuing to be rapidly developed on a daily basis. Maintaining a clean Git history can help save time and have the added benefit of being a means to track down security/app-breaking changes by greping your Git history.
Here at Rietta, we like to keep well managed git histories for not only internal reasons, but also for any potential future maintainers of our code repositories. This way, we can provide the best service to our clients while also being helpful to potential future maintainers. As mentioned above, we specifically turn off “Create a Merge Commit” and use exclusively “Rebase & Merge” and “Squash & Merge”.