The Secret of Tidy Git Repositories: What Merging and Rebasing Mean
Hi Friends.
I didn’t truly understand what I was doing when I first started using Git. The company I was at used two tools for checking code in and out: Git Bash, and Git Extensions. We used Git Bash for pushing, pulling, and rebasing. To complement this, Git Extensions was used for day-to-day actions – things like:
Looking at the commit graph
Looking at code diffs in commits
Searching
Creating local commits
I learned how to make my first commit with Git Extensions. It’s a relatively straightforward process: you click the Commit button in the toolbar, visually check and stage any files that you want committed, and then click the Commit button. This results in a local commit that can be used to mark development milestones: when starting something risky, it’s easy to reset any file changes back to the most recent commit. This brings us back to a safe state to try again.
In comparison, pushing my commits to GitHub felt like I was casting a spell. I made notes when my mentor first took me through the process. I still have a copy of the (Git Bash) command sequence that I entered for preparing my branch:
git checkout main
git pull
git checkout feature_branch
git rebase main
git checkout main
git merge feature_branch
git push
git checkout feature_branch
Ignoring that we would push directly to the main branch, I hope you can appreciate why I felt confused by the whole process. My mentor suggested that I look at the commit graph in Git Extensions after each step to understand what it was doing. Even though I did, it still felt like magic to me.
Over time, the process and what it did started to sink in. I also started noticing that my teammates didn’t rebase their branches quite as often as I did – they would often just merge their branches when getting the latest code. Instead of merging, I usually recommend rebasing a feature branch when wanting to incorporate the latest code from the main branch. To see why, it’s important to understand two concepts that we will look at this week:
Fast forward merging
The difference between merging and rebasing
Fast Forward Merging
Git performs a fast forward merge when two branches that share the same commit history are merged. Consider the repository pictured in Image 1.
The main
branch is currently at Initial commit
. Work has been done on the feature
branch, which is now two commits ahead. There is no divergence in the commit history; the branches do not fork: main
is simply behind feature
. In this case, merging main
into feature
results in a fast forward merge. We can see in Image 2 that main
has moved to feature
, and no merge commit has been created.
Merging vs Rebasing: An Analogy
Let’s imagine two friends – Alice and Bob – are looking for a new hobby. They both enjoy arts and nature, and one day come across a paint-by-numbers kit featuring an image of a toucan in a forest. They both love the image and decide to buy a kit each. Alice loves the toucan and decides to start painting it before attempting the forest scenery. On the other hand, Bob loves the forest imagery and works on that first. A little time passes. Alice has fully painted the toucan; Bob has completed the forest but hasn’t started on the toucan yet. They decide to meet with their mutual friend Carol over coffee and they both talk about their creative endeavours. Carol is excited and tells them that she can’t wait to see the final painting.
After meeting up, Alice and Bob are really excited too – they can’t wait to show their work to Carol and want to complete their project as soon as possible. They come up with two ideas. Both would showcase their skills, combining their efforts to quickly finish a single painting. As Alice’s toucan is complete, and Bob has finished painting the forest:
Alice could cut the toucan out from her painting and transfer it directly onto Bob’s work, which is complete apart from the toucan.
Alice is a quick learner and has mastered painting the toucan. She could very quickly redo what she has already learned and finish off Bob’s painting.
Of the two options, the first is like a merge: two separate pieces of work are combined to form a single composite work. The second option is like a rebase: you take work that has already been done elsewhere and reapply it from a different starting point. With either approach, you end up with an updated codebase containing a combination of both branches. However, they are subtly different: a (non-fast-forward) merge results in the branches being combined with a merge commit; rebasing a branch allows you to update it without creating a merge commit.
Summary
There are two types of merge in Git. A fast forward merge happens when a branch is merged into another, and the commits on the branch to be merged were all already in the destination branch prior to the merge. In this case no merge commit is created.
Where this is not possible, a fast forward merge cannot be made and will result in a merge commit instead.
Merging and rebasing are two strategies for updating a branch. When two branches diverge in their commits, a merge combines the branches together with a merge commit. A rebase reapplies the code changes onto another branch, resulting in a single continuous branch.
At a high level, that’s it! You now understand the differences between merging and rebasing branches in Git. In the next part of this topic, we’ll look at the advantages and disadvantages of each strategy. We’ll also discuss situations where you might want to choose one over the other.