The Git Explosion
Frustrated with other version control systems and their slow updates and commits, Linus Torvalds, of Linux kernel fame, put aside a month in 2005 to write his own. He named it Git.
Sites like GitHub, GitLab, and BitBucket have symbiotically promoted and benefited from Git. Today Git is used globally, with a massive 98 percent of 71 thousand respondents in a 2022 survey using Git as the version control system.
One of Git’s main design decisions was speed. In particular, working with branches had to be as fast as possible. Branches are a fundamental part of version control systems. A project repository will have a main or master branch. This is where the project’s code base sits. Development, such as new features, takes place in segregated side branches. This stops the work done in branches from messing up the master branch, and it allows simultaneous development to happen in different parts of the code base.
As the developments in the side branches are completed, the changes are transferred to the master branch by merging the development branch into the master branch. In other version controls systems working with branches was difficult and computationally expensive. Working with branches in Git is very fast, and very lightweight. What was once a tedious and often avoided exercise in other systems, became trivial in Git.
The Git rebase command is another way of transferring the changes from one branch into another branch. The merge and rebase commands have similar objectives, but they achieve their ends in different ways and yield slightly different results.
What Is Git merge?
So what is the Git merge command for? Let’s say you’ve created a branch called dev-branch to work on a new feature.
You make a few commits, and test your new feature. It all works well. Now you want to send your new feature to the master branch. You must be in the master branch to merge another to it.
We can ensure we’re in the master branch by explicitly checking it out before we merge.
We can now tell Git to merge the dev-branch into the current branch, which is the master branch.
Our merge is completed for us. If you checkout the master branch and compile it, it’ll have the newly developed feature in it. What Git has actually performed is a three-way merge. it compares the most recent commits in the master and dev-branch branches, and the commit in the master branch immediately before the dev-branch was created. It then performs a commit on the master branch.
Merges are considered nondestructive because they don’t delete anything and they don’t change any of the Git history. The dev-branch still exists, and none of the previous commits are altered. A new commit is created that captures the results of the three-way merge.
After the merge, our Git repository looks like a timeline with an alternative line branching off and then returning to the main timeline.
The dev-branch branch has been incorporated into the master branch.
If you have a lot of branches in one project, the history of the project can become confusing. This is often the case if a project has many contributors. Because the development effort splits into many different paths, the development history is non-linear. Untangling the commit history becomes even more difficult if branches have their own branches.
Note that if you have uncommitted changes in the master branch, you’ll need to do something with these changes before you can merge anything to it. You could create a new branch and commit the changes there, and then do the merge. You’d then need to merge your temporary branch back into the master branch.
That works, but Git has a command that achieves the same thing, without having to create new branches. The stash command stores your uncommitted changes for you, and lets you call them back with stash pop .
You’d use them like this:
The end result is a merged branch, with your unsaved changes restored.
What Is Git rebase?
The Git rebase command achieves its aims in a completely different way. It takes all of the commits from the branch you’re going to rebase and replays them onto the end of the branch you’re rebasing onto.
Taking our previous example, before we performed any action our Git repository looks like this. We have a branch called dev-branch and we want to move those changes to the master branch.
After the rebase , it looks like a single, completely linear timeline of changes.
The dev-branch has been removed, and the commits in the dev-branch have been added to the master branch. The end result is the same as if the commits in the dev-branch had actually been directly committed to the master branch in the first place. The commits aren’t just tacked onto the master branch, they’re “replayed” and added fresh.
This is why the rebase command is considered destructive. The rebased branch no longer exists as a separate branch, and the Git history of your project has been rewritten. You can’t determine at some later point which commits were originally made to the dev-branch.
However, it does leave you with a simplified, linear, history. Compared to a repository with dozens or even hundreds of branches and merges, reading the Git log or using a graphical git GUI to look at a graph of the repository, a rebased repository is a breeze to understand.
How to Rebase Onto Another Branch
Let’s try a git rebase example. We’ve got a project with a branch called new-feature. We’d rebase that branch onto the master branch like this.
First, we check that the master branch has no outstanding changes.
We checkout the new-feature branch.
We tell Git to rebase the current branch onto the master branch.
We can see that we have still got two branches.
We swap back to the master branch
We merge the new-feature branch into the current branch, which in our case is the master branch.
Interestingly, we’ve still got two branches after the final merge.
The difference is, now the head of the new-feature branch and the head of the master branch are set to point to the same commit, and the Git history doesn’t show there used to be a separate new-feature branch, apart from the branch label.
Git Rebase vs. Merge: Which One Should You Use?
It’s not a case of rebase vs. merge. They’re both powerful commands and you’ll probably use them both. That said, there are use cases where rebase doesn’t really work that well. Unpicking mistakes caused by mistakes using merge are unpleasant, but unpicking errors caused by rebase is hellish.
If you’re the only developer using a repository, there’s less chance of you doing something with rebase that is disastrous. You could still rebase in the wrong direction for example, and rebase your master branch onto your new-feature branch. To get your master branch back, you’d need to rebase again, this time from your new-feature branch to your master branch. That would restore your master branch, albeit with an odd-looking history.
Don’t use rebase on shared branches where others are likely to work. Your changes to your repository are going to cause problems to a lot of people when you push your rebased code to your remote repository.
If your project has multiple contributors, the safe thing to do is only use rebase on your local repository, and not on public branches. Likewise, if pull requests form part of your code reviews, don’t use rebase. Or at least, don’t use rebase after creating the pull request. Other developers are likely to be looking at your commits, which means that those changes are on a public branch, even if they’re not on the master branch.
The danger is that you are going to rebase commits that have already been pushed to a remote repository, and other developers might have already based work on those commits. Your local rebase will make those existing commits vanish. If you push those changes to the repository you’re not going to be popular.
Other contributors will have to go through a messy merge to get their work pushed back to the repository. If you then pull their changes back to your local repository, you’re then faced with unpicking a mess of duplicated changes.
To Rebase, or Not to Rebase?
Rebase might be outlawed in your project. There may be local, cultural objections. Some projects or organizations consider rebase as a form of heresy, and an act of desecration. Some people believe the Git history should be an inviolable, permanent record of what has happened. So, rebase might be off the table.
But, used locally, on private branches, rebase is a useful tool.
Push after you’ve rebased, and restrict it to branches where you’re the only developer. Or at least, where all development has stopped, and no one else has based any other work off your branch’s commits.
Do that and you’ll avoid any issues.
RELATED: How to Check and Update Your Git Version