Keeping our history clean with git rebase

With git rebase, instead of creating a new commit for the merge, it will try to place the changes on the feature branch as if they were made directly after the last commit on the main branch:

To see how we can work with rebase, let's repeat everything we've done so far, but using rebase instead of merge. Create a new directory and open your Terminal, then copy and paste the following commands (which will replicate everything we've done so far):

git init &&
echo -e "# hobnob" >> README.md &&
git add README.md && git commit -m "Initial commit" &&
echo "A very simple user directory API with recommendation engine" >> README.md &&
git add README.md && git commit -m "Update README.md" &&
echo "console.log('Hello World')" >> index.js &&
echo -e "# Usage\nRun \`node index.js\`" >> README.md &&
git add -A && git commit -m "Add main script and documentation" &&
git checkout -b dev master &&
git checkout -b social-login/main dev &&
touch social-login.txt &&
git add -A && git commit -m "Add a blank social-login file" &&
git checkout -b social-login/facebook social-login/main &&
echo "facebook" >> social-login.txt &&
git add -A && git commit -m "Implement Facebook login" &&
git checkout -b social-login/twitter social-login/main &&
echo "twitter" >> social-login.txt &&
git add -A && git commit -m "Implement Twitter login" &&
git checkout -b user-schema/main dev &&
touch user-schema.js &&
git add -A && git commit -m "Add User Schema" &&
git checkout dev &&
git merge user-schema/main

Our Git history tree now looks like this:

First, we can merge social-login/facebook into social-login/main. As no changes have been made on social-login/main since the branching occurred, it makes no difference whether we use git merge or git rebase:

$ git checkout social-login/main
$ git merge social-login/facebook

After our merge, there is now a change on the social-login/main branch since social-login/twitter branched out from it:

Here's where rebase is useful:

$ git checkout social-login/twitter
$ git rebase social-login/main
...
Auto-merging social-login.txt
CONFLICT (content): Merge conflict in social-login.txt
error: Failed to merge in the changes.
Patch failed at 0001 Implement Twitter login
The copy of the patch that failed is found in: .git/rebase-apply/patch

There's still going to be a merge conflict, and you should resolve it the same way as before. But this time, use git rebase --continue instead of git commit:

# Resolve merge conflict before continuing #

$ git add -A
$ git rebase --continue
Applying: Implement Twitter login

The difference is, this time, the git history for the social login feature is linear, as if the changes on the social-login/twitter branch were made straight after those on the social-login/main branch:

$ git log --graph --oneline --decorate --all
* da47828 (HEAD -> social-login/twitter) Implement Twitter login
* e6104cb (social-login/main, social-login/facebook) Implement Facebook login
* c864ea4 Add a blank social-login file
| * 8f91c9d (user-schema/main, dev) Add User Schema
|/
* d128cc6 (master) Add main script and documentation
* 7b78b0c Update README.md
* d9056a3 Initial commit

Next, we need to fast-forward the social-login/main branch to follow the social-login/twitter branch:

$ git checkout social-login/main
$ git merge social-login/twitter

This should produce a much cleaner branch structure:

Lastly, we can rebase our social-login/main branch onto the dev branch:

$ git checkout social-login/main
$ git rebase dev

Now, we have a completely linear commit history on the social-login/main branch, even though they all originated from different branches:

The last thing to do is to forward the dev branch to where the social-login/main branch is:

$ git checkout dev
$ git merge social-login/main