This post is from the CollabNet VersionOne blog and has not been updated since the original publish date.
GitEye and Interactive Rebase
As I mentioned in my earlier blog, TeamForge for Gerrit, a Gerrit patch set must be associated with one and only one commit. In this blog I will talk about how you can commit your work periodically as you work on your change request and later use the interactive rebase feature in GitEye to squash your commits into one commit, and to compose a commit message suitable for Gerrit.
I have cloned a TeamForge Git repository and configured it for Gerrit as described in my earlier blog. Also following the steps described in the earlier blog I have created and checked out a local branch for a TeamForge artifact. As I worked on the artifact, I periodically committed my changes to the local repository. This not only gave me a sense of accomplishment, but it also gave me good checkpoints in the event that I made a mess and needed to roll back. The following screenshot shows the repository history after these commits.
As you can see, I have made five commits since cloning the repository, starting with Create database and ending with Create the UI.
Now I am ready to push my work to Gerrit to request a code review. However, Gerrit insists that my change must consist of just one commit!
Fortunately, Git has a tool called interactive rebase that can do exactly what I need to do, which is to squash all of my commits into a single commit. Furthermore, GitEye includes a slick graphical interface for this tool.
To start interactive rebase from the History view, right-click on the commit preceding the oldest commit you want to rewrite. File the previous sentence in your memory bank, as not knowing where to start an interactive rebase is a frequent cause of frustration for beginners.
When you start interactive rebase an editor will be opened that lets you define a rebase plan.
To understand what you are doing as you define your rebase plan, it helps to know what is going to happen when you finish your plan and click Start.
- GitEye will rewind HEAD to the commit preceding the first one in the edit list.
- GitEye will process your plan, starting from the top row and working towards the bottom.
But what does process your plan mean? It means that for each row it will perform the action that you have selected. The available actions are described below:
- pick Include commit. Pick entries can be moved up or down to reorder commits.
- skip Do not include commit. It will be removed from history.
- edit Amend commit. You will be asked for input when an edit entry is encountered.
- reword Include commit, but edit commit message. You will be asked for input.
- squash Squash commit with its predecessor commit, including their commit messages.
- fixup Squash commit with its predecessor commit, discarding squashed commit’s commit message.
So, for example, if you were to click Start without making any changes to the default plan, the net effect would be zero. HEAD would be rewound to the commit that you selected from the History view, and then the PICK actions would cause the commits to be reapplied in the same order that they were originally made, leaving you right back where you started.
With this in mind, let’s plan a strategy for preparing our commits to be pushed to Gerrit.
As I committed my changes, I didn’t worry too much about my commit messages. Git repositories are local, so for the time being I knew that the messages were strictly for my own eyes and use. I knew that when I was ready to push my changes I could use interactive rebase to adjust my commit message for Gerrit and the world at large. To do this, I will select the first commit in my plan’s edit list and I will click on the Reword button. The action column will be updated to reflect the change.
At this point, I haven’t done anything to actually change my commit message but, when I eventually click Start, the REWORD entry will cause the rebase process to pause to allow me to edit the commit message for that commit.
Since I will be providing a Gerrit-appropriate commit message for the first commit, all I need to do now is squash the remaining commits into that one. I will use the Fixup option because my intent is to discard all the commit messages except for the first. I select all the commits except for the first and I click Fixup. Again, the action column is updated to reflect the change.
Now that I have defined my rebase plan, it is time to execute it. To do this, I click on the Start button. Because I chose REWORD for the first entry, I will immediately be prompted to edit the commit message.
In this example, there is only one rebase plan entry that requires input, but you can tell which entry you are being prompted for because it is bold in the edit list, as you can see above.
In the above screenshot, I have formatted the first row of the commit message so as to cause an association to be created between my artifact and my commit (see my blog, Linking TeamForge Commits to Artifacts for a detailed explanation). Because I included a Change-Id in my original commit, I can leave the last line in the commit message as it is. To create a Gerrit change request, this Change-Id row must be included in the commit message when pushing to Gerrit (see my blog, TeamForge for Gerrit for a detailed explanation).
Once my commit message is the way I want it, I click OK.
Because none of my other rebase plan entries require user intervention, the next thing that I will see is notification that the rebase was successful.
Notice that the label decoration of the repository now indicates that there is just one outgoing commit whereas, if you refer back to the first screenshot, you will see that there were originally five. We can further confirm that our interactive rebase was successful by looking again at the History view.
Now that I have a single commit that includes all my work as well as a Gerrit Change-Id in the commit message, I can go ahead and push my commit.
By pushing my commit to Gerrit, I have created a new Gerrit change.
Let’s look at a similar example, only this time I will use Squash rather than Fixup when defining my interactive rebase plan.
In this example I am working on another TeamForge artifact which represents a request that copyright information be added to the header of all the files that I created in the previous example. In the screenshot below, you can see that I have made four commits since creating the local branch for my artifact.
Once again, I will open the History view, select the commit preceding the oldest commit that I want to rewrite, and then select Rebase Interactive from the context menu. Again I will use REWORD for the first entry, but this time I will use SQUASH for the remaining entries.
When I click Start, the rebase will pause on the last squash in the block of commits that are being squashed together.
At this point, the commit message is a bit of a mess. However, the text includes all four of the message from my commits, along with comments indicating which message belongs to which commit. All I have to do now is use cut and paste to arrange them how I want. Also, you’ll notice that I included a Change-Id in each of my four commits. I’ll be sure to delete all but one of these. I’ll also remove all the comments. My fixed commit message looks like this:
When I click OK, the interactive rebase completes successfully and I can see in the History view that, once again, my commits have been squashed into a single commit that is ready to be pushed to Gerrit.
Interactive rebase is a powerful tool and I have only given you a small taste. It can be used to fix problems but it can also be used to create problems! Keep in mind that if you are working on an interactive rebase and it gets out of control, you can always click on Abort to stop the rebase and roll back to the starting point. In this blog I have used interactive rebase to prepare commits for Gerrit because that is a situation in which the need often arises, but interactive rebase can be useful as a general Git tool, and is in no way limited to Gerrit. You should not, however, use interactive rebase to rewrite commits that you have already pushed to a real (non-review) branch of a remote repository. Your colleagues will hate you for it, and you will end up agreeing with them.