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 dive into those discussions and is purely meant to walk through the basics of using interactive rebasing. not 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 rather than . git commit --amend 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: From the git log above, here are the two commits we want to change: - In this commit we accidentally committed a merge conflict - In this commit we removed the merge conflict 4a4d705 6c01350 What we would like to do is go back in time to , remove the merge conflict in the commit, then delete 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: 4a4d705 6c01350 We won’t have broken commits (merge conflicts) 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 , 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.” Pro Git 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: — The last commit we want to retain as-is has a SHA-1 of so we can pass this into our interactive rebase command. - 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 to our interactive rebase command. By SHA-1 528f82e By Index HEAD~3 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: 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 that you want to edit and then hit the key (for insert mode). Once you’re done typing hit the key to exit insert mode. pick <i> <esc> In our example, we have changed the command to for the commit we want to modify and we have changed the command to for the commit we want delete. Then we run to save and quit the Vim window. edit drop :wq Amend the commit Back in the terminal we see this message: This makes sense that we are stopped at . 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. 4a4d705 As a reminder, was the commit with the merge conflict we accidentally committed. When we open up our editor we see the merge conflict there: 4a4d705 So we fix the merge conflict in the file but what do we do now? When in doubt, : git status Cool! This is actually helpful. We see that we are currently editing , and we see the next two commits to be acted upon after this one. 4a4d705 The rest of the message is explaining a familiar workflow to us. Git tells us if we want to amend the commit we run . This will essentially act as our typical 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. git commit --amend git commit All we do is to stage the edited file and then to commit the staged file! This will now open a Vim window again with the commit message: git add tempChanger.js git commit --amend 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 to save and quit the window. :wq We have now edited our old commit. So now what? Run : git status Continue interactive rebase We don’t have anything else to change in the commit so let’s continue! We run and we see the following message: git rebase --continue Woah, we’re done? But what about those other two commits? Well, the next commit to be acted on was . This commit we marked to delete ( ) when we started the interactive rebase. Git automatically deleted it and moved onto the next commit, . This one was never modified in the initial interactive rebase. Its default command was which means the commit will be used. Git applied that commit and since that was the last commit, the interactive rebase completed. 6c01350 drop 41aa9d2 pick 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 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. git rebase --abort After interactive rebase Our git log now looks like: You will notice a few things have changed from before we started the interactive rebase: We no longer have the commit with the commit message “Remove merge conflict”. This is the commit we deleted in our interactive rebase. 6c01350 Our commit we edited, , has a different SHA-1, . 4a4d705 2b7443d Our most recent commit from our original git log, , also has a new SHA-1, . This commit was not changed but was simply applied to the commit before it . 41aa9d2 2b95e92 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, , where neither the commit message nor the files changed at all. This brings up an important point with interactive rebasing: 2b95e92 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: Technically, you could get around this by using 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. git push --force Another solution is to use 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. git push --force-with-lease When to use 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 . --force-with-lease git push --force-with-lease here 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. Written by Blake DeBoer. Originally posted on Dev.to Senior, Lead, or Principal developer in NYC? Stride is hiring ! Want to level up your tech team? See how we do it ! www.stridenyc.com
Share Your Thoughts