Like many tools, the Git version control system, through a handful of commands, provides the core functionality that most users need on a daily basis. In today’s post, I will touch on one that doesn’t seem to be used as often or, for many users, hasn’t been used at all.
Making One of Two
One of Git’s primary features, that was a huge draw for me when first exposed to it, is branching. Unlike many other systems, branching in Git is simple, fast, and the expected procedure to use for introducing new functionality, fixing bugs, and more.
If you are working alone in a repository, there usually isn’t much of a need to do more than simple branch and merge operations. However, Wwhen you add more contributors, however, managing the work can become a bit more involved, but still be easily maintained with the branch and merge commands.
A Common Occurrence
Let’s say you’ve created a new feature branch off the dev branch, done some work (with commits) locally in that branch, then see that someone else working in a different feature branch has merged their work into the dev branch.
You might continue work in your feature branch because the new code doesn’t affect what you’re implementing and vice versa. But often, you will find yourself wanting to incorporate changes and fixes into your current working branch.
At this point, most will reach for the merge command which will definitely do the job it’s intended to: incorporate changes from another branch into the current one. But if you find yourself (or other team members) doing this often, you can wind up with quite a few merge commits which, among other things, can make it a bit more difficult to understand the history of the project. Merge commits don’t go away when you eventually fold your feature branch back into dev.
While merging is a common workflow for handling these kinds of scenarios, another option is the rebase command.
The rebase command performs the same work as a merge operation, but in a way that results in a different historical view.
Using the scenario from above, how would things look if you reached for rebase instead of merge?
When you choose to rebase the changes in your feature branch onto those that have been committed in another branch (such as the dev branch your feature branch is based on), the command goes back to the common ancestor snapshot saving the feature branch commits to temporary files. It then switches to the branch you’re rebasing (dev), resetting HEAD to the most recent commit, then replaying those stored commits from your feature branch and reinstating the feature branch.
Now, a look at the history will appear to show that the work you’ve done in the feature branch came after the commits (done in parallel) in the dev branch. No merge commits will “litter” the history of either branch.
While this workflow may seem only useful for resulting in clean commit histories, Scott Chacon (Pro-Git) makes a good point that it also provides value when working in a repository that you don’t maintain. By rebasing your work on origin/dev, for example, the maintainer does not need to do any integration work to incorporate your changes; just a fast-forward merge.
The Golden Rule
There is absolutely at least one time when you will not want to employ the rebase command: when your commits have been published outside of your repository.
If you’ve pushed local commits to a remote, do not use the rebase command. It can cause lots of pain and suffering for your teammates (even though there are ways to work around it).
To rebase or not to rebase…
Most people are comfortable with just using the merge command for combining branch work. It’s a completely serviceable practice and, as they will often argue, shows the “true” history of the repository compared to rebasing.
I often reach for rebase when hot fixes or other commits that contain changes I’d like to have while working in a feature branch are made before I’m done. The feature branch will be clear of those merge commits letting me see a clear history of the work and will maintain that clarity when it is merged back into the destination branch.