life coded

a personal blog by Maximilian Ehlers

Interactive rebasing with git

First post oh yeah

Finally I have found the time to start writing a post. To start off this Blog I want to write about some tips and tricks that help me in my daily programming endeavours. The first one will be

git rebase -i

 git rebase -i

stands for interactive rebasing.

This can help you when you want to add some changes to an earlier commit for example, which can aid alot with code reviews.

Lets explain it with the following scenario

First step

Create the file A with some content in it

touch A
echo Hello > A

Now lets commit this

git add A
git commit -m 'added A'

Second Step

Now that we have created A we are going to work on the next feature which is B.
So lets create it, put some content in it and then commit the changes.

touch B
echo "B is great" > B
git add B
git commit -m 'added B'

So far so boring, but now comes the fun part.
We forgot to add the important second line to A. Without it the first commit is pretty much useless. We have two options now. We can create a new commit with the changes and then push everything for review. This is pretty straight forward but comes with the cost of having multiple commits for one logical change that are not in order, which means that the reviewer might need to spend a lot more time understanding what we have done.

Even worse he/she might flag errors in specific commits that we already fixed in later ones, thus causing unnecessary work.

This is where the interactive rebase comes in handy.

Third step

Just as in the first scenario we first create a new commit with the changes we need to make

echo "important second line" >> A
git add A
git commit -m 'added important second line to A'

Instead of pushing these changes for review we will now execute the command

git rebase -i HEAD~3

If you are following these steps in a new repository you will have to call git rebase -i --root, because git does not allow a change to the root commit by default

This will start the interactive rebase for the last 3 commits and should open the following in the Editor you have set up.

  1 pick 230f033 added A¬
  2 pick c0a7ba1 added b¬
  3 pick 0388210 added important second line to A¬
  4 ¬
  5 # Rebase 0388210 onto 3da42fc (3 commands)¬
  6 #¬
  7 # Commands:¬
  8 # p, pick = use commit¬
  9 # r, reword = use commit, but edit the commit message¬
 10 # e, edit = use commit, but stop for amending¬
 11 # s, squash = use commit, but meld into previous commit¬
 12 # f, fixup = like "squash", but discard this commit's log message¬
 13 # x, exec = run command (the rest of the line) using shell¬
 14 # d, drop = remove commit¬
 15 #¬
 16 # These lines can be re-ordered; they are executed from top to bottom.¬
 17 #¬
 18 # If you remove a line here THAT COMMIT WILL BE LOST.¬
 19 #¬
 20 # However, if you remove everything, the rebase will be aborted.¬
 21 #¬
 22 # Note that empty commits are commented out¬

When you read the comments that git displays here you can easily spot the squash functionality.

We can use this to squash our two commits 1 and 3 together and make it look like they have been one commit all along. Making it easier to keep all the logic in one place for the reviewer.

So swap the lines #2 and #3 and change the pick on the line #2 to squash (or s).

It should now look like this

1 pick 230f033 added A¬
2 squash 0388210 added important second line to A¬
3 pick c0a7ba1 added b¬

Now save this and close your editor ( I use vim, so here just type :wq).

Git will now perform the rebase and then open a new Editor window
where we can change the commit message, it should look like this

  1 # This is a combination of 2 commits.¬
  2 # This is the 1st commit message:¬
  3 ¬
  4 added a¬
  5 ¬
  6 # This is the commit message #2:¬
  7 ¬
  8 added important second line to A¬

Lets Change the message to the following

 1 added A with important second line¬

Save and close the editor again.

If you want to skip editing the commit message and just keep the first one you can use fix instead of squash in the interactive rebase

Lets look at our git history

git log

commit a4e6e5c5f9cfb9167693e0067308b02af1da5543
Author: bananenmannfrau <ehlers.berlin>
Date:   Thu Oct 13 20:52:19 2016 +0200

    added b

commit 34b65b85a0879093f337e4473a19c01ed8fd6ad2
Author: bananenmannfrau <ehlers.berlin>
Date:   Thu Oct 13 20:52:10 2016 +0200

    added A with important second line

Et voilà. Our commits now represent changes that belong together, which will make it alot easier to review. Now we can feel at ease when pushing this and creating our pull request.

I hope that this might help you some time in the future.
Despite the usefulnes of git rebase -i you should try to keep your stories small though. That way you wont even have to have different commits with their own logical scope in one pull request.

Update and Warning:

As u/Heroxis mentioned when I posted a link to this article on r/programming you should be carfeul when using this on already pushed commits.
If you would change the git history with the interactive rebase and try to push it, it will result in errors and the push will be rejected. This is due to the fact that the history will be different on the remote server and on your local machine. You can use git push --force to push anyways and overwrite the history on the remote server. But be careful with this if you do not know what exactly will happen. Try to ask someone more experienced first. It could potentially destroy a lot of work on the remote server if configured 'badly'.