My last post was focused around a problem in Git which occurs when the rebase command isn't used, and developers blindly use pull/push. The problem is that it creates pointless merge commits, and also stops the Git history from being linear, ie. the branches are diverging and remerging back in all the time. It makes the history extremely hard to read and understand.
That post only talked about a very simple rebase to avoid that particular problem. After getting feedback from a colleague, it was suggested about also mentioning squashing commits as another way of tidying up the commit history. As that would have then moved into the topic of interactive rebasing, I felt that this was beyond the scope of the particular problem I was trying to discuss. However, it does fit nicely into a follow up post about more advanced Git rebasing! ...
As mentioned in my last post, a rebase is where Git replays commits to change the Git history in some way or another. I only covered a very basic rebase to tidy up the local commits before pushing them and get rid of the merge commit. However, there are other changes you may need to do to tidy up your local commits before pushing. Interactive rebasing gives you much more power by giving you a list of commits that are about to be replayed, and asking you want you want to do with those commits. Here are some examples of what you can do ...
The golden rule still applies in that you should only modify the history of commits that haven't been pushed, as this can cause bad headaches for other developers!
An interactive rebase is started with the -i
flag. So from the command line, you could do this:
git rebase -i origin/master
In this example, I put 'origin/master'. However that could have been any of Git's many ways of referencing a commit - a branch reference, a commit id (SHA), etc.
By choosing 'origin/master', it will replay all the local unpushed commits in the master branch - ie. starting the replaying of commits from the 'origin/master' commit. In most scenarios where I'm doing an interactive rebase, I tend to always do this.
When you start an interactive rebase, your default text editor will pop up with a list of the commits it's going to replay. Looking something like this ...
pick c1d7428 Started a feature
pick f4f05cd Fixed a bug
pick 6c59924 Further work on feature
pick 55af68f Hotfix for issue 26
pick b1adb19 Refactoring
# Rebase cfc0064..b1adb19 onto cfc0064 (5 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
The lines starting with a hash symbol (or 'pound' symbol depending on where you live!) are just comments to help you out. It's the lines at the top which are important. These are the commits that are about to be replayed (displayed in ascending order). If you close this without making any changes, then it'll do the same as a non-interactive rebase and just replay them as is.
To modify what the rebase will do, all you do is edit the word at the start of each line. The default value of 'pick' which you see in the example above just means to use that commit - hence why if you don't edit this file, it'll do a normal replay of original commits.
The reference in the comments at the bottom of the file do a pretty good job of summarising what each command does, so I won't echo what's already said there. Just remember that the list of commits is ascending, not descending - so if for example, you wanted to combine two commits using the squash command, you would replace the 'pick' in the line of the second commit with 'squash' (or just 's' for short).
If you're editing a commit message, then don't try to edit the commit message in this file - you'll be prompted for the new commit message shortly.
Once happy, close the file, saving the changes. Git will detect that the file has been closed, and will begin the rebase. If you've requested any changes that require further input - eg. changing a commit message - then your default editor will be reopened asking you for the new commit message. Save and close this file, and the rebase will continue.
If you choose to edit a commit (by changing 'pick' to 'edit'), then the rebase will stop when you get to this commit. You can then make any changes you want. One caveat is that when it stops, the commit you're editing has already been committed. So after the interactive rebase stops, the first thing you'll usually want to do is reset that commit with this command:
git reset HEAD^
This will un-commit the latest commit, but leave its changes in the working directory, so you can then do what you want with them - eg. perform multiple commits; remove specific changes, etc.
As you can see, adding the interactive rebase to your toolbox of Git skills can be very useful. And it's actually really easy - you just tell Git what to do and it does it for you.