There is no denying that Git is a power tool. Power tools command discipline and mastery to be used effectively.
Git –rebase is an indispensable feature on this power tool, and when used correctly can provide many benefits.
The Value of a Clean Commit History
As programmers we value clean code. We follow standards, use linters and do code reviews, to name just a few ways we facilitate this. So why put up with a less than perfect git history?
One could argue that the commit history of a project is less likely to change than the source code itself. The commit history contains valuable metadata about the project, and should be treated as such.
When you have a few developers working on the same project, there are guaranteed to be merge conflicts. This can lead to a lot of merging, and creating noisy merge bubbles and a bloated commit history. You could say that explicit merges have an adverse effect on the conciseness of our commit history.
When you do continuous integration, the goal is to keep each commit atomic and passing the tests. With this in place, you have great flexibility in managing your project. You could benchmark each commit to keep an eye on performance regressions, roll back to a previous commit and deploy it out to production with confidence, or run a Git bisect to find bugs.
Thought-provoking metadata tends to just fall out of a clean linear Git history.
Since Git is distributed, we all get our own copy of the entire history of the repository. You can do a Git pull (composed of git fetch && git merge), or you can pull and rebase the changes.
The difference is that the rebase will replay your local commits on top of the new commits from fellow team members, while merge preserves the exact commits. Rebase does not preserve metadata about the exact time that a commit was made, and only cares about when the commits were pushed.
Both histories are preserved. This creates a divergence and leaves an undesirable “merge bubble” as seen above.
Pull & Rebase
The resulting modification to your source code is identical to the non-rebase approach. The only difference is in the recorded metadata.
Merging Under the Hood
When you do a normal merge on diverged commits, Git takes 3 commits into consideration (Also known as a 3-way merge). Your current commit, the tip of the branch to be merged, and the base parent.
When you rebase, Git will change the base parent to be the tip of the other branch, and replay the commits on top of that.
Isn’t using rebase changing the history of what’s already been done (which is obviously bad)? Coming from a mercurial background, I feel almost compelled to agree with this, but it is actually not at all that uncommon in the Git world.
There are strong, conflicting opinions on this topic, and I won’t expand on it too much but I think it’s a reasonable thing to do. There is a lot of valuable information recorded by a Git commit, but most merge commits do not contain anything of significant value.
When Not to Rebase
When you want to preserve explicit merges.
At times you may want to keep a commit that represents the action of resolving the merge conflicts. I cannot think of a situation where this information would be valuable in terms of a build pipeline, but you may want it for debugging purposes.
Dealing with Git Rebase Merge Conflicts
Dealing with conflicts when the rebase flag is set is a little different from regular merge conflicts.
The first big difference is that you are dropped onto a temporary branch where you should resolve your conflicts. You can use
git mergetool to use the editor defined in your Git config, or you can correct the conflicts manually.
Either way, once done, you need to add your file to the index when you are done. This is the way that you signal to Git that you have resolved the conflicts.
Another important thing to note is:
You should not commit your file after adding it. Also (from the rebase help page): “Commits in HEAD which introduce the same textual changes as a commit in HEAD.. are omitted”.
git rebase --continue as instructed in the prompt. Git will do what it needs to to rebase your work on top of the other commits. If all went well, you will be back on the branch you were working on with no outstanding commits, and a clean commit history.