Beginner's Guide to Interactive Rebasing
Tags: git, beginners, tutorial, gitflow
Published: 2018-01-08 16:00:59

---
title: Beginner's Guide to Interactive Rebasing
published: true
description: step-by-step guide to using interactive rebasing
tags: git, beginners, tutorial, gitflow
image: https://thepracticaldev.s3.amazonaws.com/i/c1aigszmnfhgueiqk889.png
---

Earlier this year I did an interactive rebase for the first time and I was impressed by what one can do with it. I also found it to be a little complex at first. Hopefully this guide will help remove some uncertainty around it.

Also, because it is so powerful and you can essentially rewrite history, a small warning before we begin:  There are many schools of thought on Git and whether rebasing is a good idea or not. This post will **not** dive into those discussions and is purely meant to walk through the basics of using interactive rebasing.

---

## TL;DR

* Interactive rebasing can be used for changing commits in many ways such as editing, deleting, and squashing.
* To tell Git where to start the interactive rebase, use the SHA-1 or index of the commit that immediately precedes the commit you want to modify.
* During an interactive rebase, when Git pauses at a commit you tagged to edit, the workflow is no different than a normal commit process - you stage files and then commit them. The only difference is you use the command `git commit --amend` rather than `git commit`.
* Interactive rebasing will create new SHA-1’s therefore it is best to use interactive rebasing on commits you have not pushed to a remote branch.

---

## The Problem
In this example, we will work through a situation where we have been working in a feature branch and we have a couple commits we would like to change or delete. Here is what our  git log looks like: 

![original git log](https://thepracticaldev.s3.amazonaws.com/i/988x2cnwvsj5rm7jcgvn.png)

From the git log above, here are the two commits we want to change: 
`4a4d705` - In this commit we accidentally committed a merge conflict
`6c01350` - In this commit we removed the merge conflict

What we would like to do is go back in time to `4a4d705`, remove the merge conflict in the commit, then delete `6c01350` since the merge conflict is resolved and we no longer need this commit. This will be an improvement to our commit history for two reasons: 
1. We won’t have broken commits (merge conflicts) 
2. We will only have meaningful commits, no commits related to solely fixing a missed merge conflict


---

## The Solution

This situation is a good candidate for interactive rebasing. Scott Chacon, in his book [_Pro Git_](https://git-scm.com/docs/git-rebase), describes interactive rebasing as follows: "Sometimes the thing fixed ... cannot be amended to the not-quite perfect commit it fixes, because that commit is buried deeply in a patch series. That is exactly what interactive rebase is for: use it after plenty of [work has been committed], by rearranging and editing commits, and squashing multiple commits into one."

### What commits do we want to modify?

To start an interactive rebase, we need to tell Git which commits we want to modify. We do this by referencing the commit immediately prior to the earliest commit we want to modify. Or, put simply, we reference “the last commit [we] want to retain as-is,” according to Scott Chacon.

Let’s look at our example to have a better understanding. There are two ways to reference this commit:
_By SHA-1_ - The last commit we want to retain as-is has a SHA-1 of `528f82e` so we can pass this into our interactive rebase command.
_By Index_ - The last commit we want to retain as-is has an index position of 3 (Git uses zero-based indexing) so we can pass in `HEAD~3` to our interactive rebase command.

Note - If you only have a few commits on which to interactive rebase, using the index is probably easier for most people. However, if you have many commits, using the SHA-1 is probably easier so you don’t have to count all the way down the git log.

### Start interactive rebasing

Based on our example, we will run either:

`$ git rebase -i 528f82e`

Or

`$ git rebase -i HEAD~3`

Which opens up this window in Vim:

![Vim window with list of unchanged commits](https://thepracticaldev.s3.amazonaws.com/i/rkaoibwx35vox2lnnib1.png)

Notice the commits are in the opposite order of git log. In git log the most recent commit is on top. In this view, the most recent commit is on bottom. Also notice the comments below give a helpful list of the valid commands we can use on each commit. 

If you don’t know Vim, just click on each word `pick` that you want to edit and then hit the `<i>` key (for insert mode). Once you’re done typing hit the `<esc>` key to exit insert mode.

![Vim window with list of changed commits](https://thepracticaldev.s3.amazonaws.com/i/c1aigszmnfhgueiqk889.png)

In our example, we have changed the command to `edit` for the commit we want to modify and we have changed the command to `drop` for the commit we want delete. Then we run `:wq` to save and quit the Vim window.

### Amend the commit

Back in the terminal we see this message:

![Git rebase interactive initial message](https://thepracticaldev.s3.amazonaws.com/i/llukgvquzhmuctes7ieh.png)

This makes sense that we are stopped at `4a4d705`. This is the oldest commit in the series of commits we want to modify. We will begin with this commit and work our way through each commit until the most recent.

As a reminder, `4a4d705` was the commit with the merge conflict we accidentally committed. When we open up our editor we see the merge conflict there:

![tempChanger.js file contents](https://thepracticaldev.s3.amazonaws.com/i/smawq2kyftwj6uf0wnfu.png)

So we fix the merge conflict in the file but what do we do now? When in doubt, `git status`:  

![Git status after changing file](https://thepracticaldev.s3.amazonaws.com/i/zvdwlz7xrhmv3qwhz1qf.png)

Cool! This is actually helpful. We see that we are currently editing `4a4d705`, and we see the next two commits to be acted upon after this one. 

The rest of the message is explaining a familiar workflow to us. Git tells us if we want to amend the commit we run `git commit --amend`. This will essentially act as our typical `git commit` we use in a normal workflow. At the bottom of this message we see our file was modified reflecting the changes we just made to remove the merge conflict. We need to stage the file before we commit. This no different than a normal workflow.

All we do is `git add tempChanger.js` to stage the edited file and then `git commit --amend` to commit the staged file! This will now open a Vim window again with the commit message:

![Vim window of first commit](https://thepracticaldev.s3.amazonaws.com/i/o00scj3tzt7ka7rzyguc.png)

We can either edit the commit message or leave as is. Let’s choose to keep the commit message the same and we will type `:wq` to save and quit the window.

We have now edited our old commit. So now what? Run `git status`:

![Git status after amending first commit](https://thepracticaldev.s3.amazonaws.com/i/kedl2zblk64v1yuzwwjb.png)

### Continue interactive rebase

We don't have anything else to change in the commit so let's continue!

We run `git rebase --continue` and we see the following message:

![Git rebase --continue](https://thepracticaldev.s3.amazonaws.com/i/j6rnf40sv2vseg44ntg2.png)

Woah, we’re done? But what about those other two commits? Well, the next commit to be acted on was `6c01350`. This commit we marked to delete (`drop`) when we started the interactive rebase. Git automatically deleted it and moved onto the next commit, `41aa9d2`. This one was never modified in the initial interactive rebase. Its default command was `pick` which means the commit will be used. Git applied that commit and since that was the last commit, the interactive rebase completed.

Note, if we had more commits to edit, we would’ve simply moved onto the next commit and started the process of amending it just like we did above. The cycle continues until there are no commits left.

### The eject button

It is worth noting if at any point in our interactive rebase we screw things up and don’t know how to fix them, we can always abort. At any point we can run `git rebase --abort` in the terminal and the interactive rebase will be aborted with no changes saved. We would then need to start the interactive rebase over again.

### After interactive rebase

Our git log now looks like:

![Git log after interactive rebase](https://thepracticaldev.s3.amazonaws.com/i/j8w5aayq4g6qivlw6duy.png)

You will notice a few things have changed from before we started the interactive rebase:
* We no longer have the commit `6c01350` with the commit message “Remove merge conflict”. This is the commit we deleted in our interactive rebase.
* Our commit we edited, `4a4d705`, has a different SHA-1, `2b7443d`. 
* Our most recent commit from our original git log, `41aa9d2`, also has a new SHA-1, `2b95e92`. This commit was not changed but was simply applied to the commit before it `2b7443d`.

### Side effects of interactive rebasing

For the two most recent commits in our git log, because they have new SHA-1’s, Git sees them as entirely new commits. This is even true of our last commit, `2b95e92`, where neither the commit message nor the files changed at all. This brings up an important point with interactive rebasing: **If you modify a commit, that commit and all successive commits will have new SHA-1’s.** 

This won’t affect anything if the commits that you have modified haven’t been pushed to a remote branch. However, if you did in fact complete an interactive rebase on commits that were already pushed to a remote branch and then pushed your branch again you would see:

![Git push error](https://thepracticaldev.s3.amazonaws.com/i/ylp8qb1p9k3tbefjy1oe.png)

Technically, you could get around this by using `git push --force` but that is very dangerous. If the remote branch has commits from other people but your local branch does not have those commits yet, you will effectively delete their commits.

Another solution is to use `git push --force-with-lease` which will only modify your commits but not commits belonging to others, though this can also be problematic. For example, if another developer already has those commits that were given new SHA-1’s on their local branch, when they pull the remote branch, they will have merge conflicts with each of these commits.

When to use `--force-with-lease` is beyond the scope of this blog post but it would be best to consult other members of your team before doing so. You can read more about `git push --force-with-lease` [here](https://git-scm.com/docs/git-push).

The key takeaway from this section is it is much easier and safer to use interactive rebasing on commits that have not yet been pushed to a remote branch.

---

_Senior, Lead, or Principal developer in NYC? [Stride is hiring](https://www.stridenyc.com/careers)! Want to level up your tech team? [See how we do it](https://www.stridenyc.com/our-work)! www.stridenyc.com_