This is part 3 of my serie on How I Git it. You can read Part 2 here.

Pause the replay, and make changes to a commit

Let’s say I’ve received some feedback in code review, and now I want to improve my code from a couple of commits ago. I could use the fixup approach shown above, but I could also choose to checkout that commit and edit it in place. I like that approach better when I’m going to make substantial changes, and not just minor fixes.

If I want to edit the highlighted commit:

Git history showing commit to be edited

I’ll enter the rebase dialog and choose “edit” (or “e").

Rebase interactive dialog showing commit marked for editing

After saving and closing the dialog, the rebase process will pause on that commit, and my changes in the two other commits won’t have happened yet. Now I can focus on my code as it was at the time of writing.

So I made some changes, everything compiles, tests are passing and I’m happy. Now to complete the process, I open up the commit dialog and I’ll stage all the changes:

Git Extensions dialog showing staged changes

I won’t commit it this time!

Next I’ll close the commit dialog.

Git Extensions will be showing a little warning in the bottom, saying I’m in the middle of a rebase.

Git Extensions showing rebase warning

To continue the rebase process I can click that warning and I’ll get some options. I’ve only ever used the continue and abort buttons.

Continuing, I’ll now have the opportunity to update my commit message as well.

Text editor showing commit message editable

Save and close to continue, as usual.

That’s it! Now my changes have been successfully woven into history.

Git Extensions showing that the changes are part of the history now

Now, I think it’s appropriate to mention that using this technique very often leads to merge conflicts with the subsequent commits. But that’s totally fine. Bring ‘em on. They are just a fact of life, and better that I, who has all the context and knowledge of the code, spend some time solving them now, instead of confusing some poor team member in a code review later down the road.

That said, one reason why I prefer this technique over squash or fixup, is that I often find the merge conflicts easier to solve using this technique.

How to solve merge conflicts is a bit out of scope for this blog post, so I’ll just say that I prefer solving them using a text editor and no fancy stuff like KDiff.

Visual Studio Code is nice though, as it got some useful links that I often use:

Visual Studio Code showing inline conflict resolution actions

But that’s me. I don’t have much opinion on how to solve merge conflicts, as long as they get solved correctly.

Pause the replay, and make a new commit in the middle of the commit graph.

In the previous section about editing a commit, it was important to not commit the changes but just stage them. If instead I commit them:

Git Extensions commit dialog showing changes about to be comitted

I’ll commit it this time

a new commit will be made, like so:

Git Extensions showing new commit made in middle of commit graph

Simple as that

This technique is not something I do very often, but again, in some situations I find it easier to use, as an alternative to moving a commit that will cause merge conflicts.

One benefit of doing these things over and over again, is that I keep Git in mind at all times. I know how to avoid bad situations that will cause a lot of merge conflicts, and I’m not surprised nor demoralized when conflicts do happen. I’m also less inclined to chase down every rabbit hole. But when I do give in to the temptation, I’ll make sure those changes are commits of their own, so as not to create noise around my main task.

I can recommend Chris Beam - How to Write a Git Commit Message (except, as you might guess, that last part about going command-line all the way).

Pause the replay, and split a commit into two or more commits.

I used this technique when writing the previous section. I had one commit that I wanted to split in two. It’s a technique I use a lot. Maybe I mixed refactoring and changes to business logic in the same commit, in which case I really really want this to be two commits. Refactoring steals focus from the more important business logic. When I make a mistake or a bad decision, I want my changes to be as clear and easy to understand as possible, to increase the chance of a team member noticing and giving feedback. Mixing in refactoring decrease the chance of someone noticing important changes (so if you want to sneak in some changes to a code base, that’s the way to go. Not that I approve though).

Okay enough talk, let’s do this. I’ll start by finding the commit I want to split, and then right click and choosing Advanced > Edit commit.

Git Extensions showing advanced menu to edit a commit

did I mention Git Extensions is great?

That’s exactly the same thing as interactive rebase and choosing edit for the commit. It’s just faster.

For the next part I use the command-line! (I haven’t found how to do it in Git Extensions yet, but I haven’t really looked either).

git reset --soft head~1

The command will do a soft reset of the commit I’m at. So when I stopped the interactive rebase at the commit I wanted to split, that’s the commit that will be soft reset. The –soft part is very important to not forget, because it tells git to not actually reset the files. Git will just kind of undo the commit action, so that the commit message is gone, and I am left in the state right before I committed, with all my files being staged.

head~1 tells the reset command to go back 1 commit.

Showing Git Bash and how it’s opened from Git Extensions by a button

Git bash is easily opened from the highlighted git icon

So now I’m in the middle of a rebase again, with my files staged and ready for commit. My goal was to split that commit that I just undid, so to do that, I will make two commits by staging only the changes I want part of the first commit.

Commit dialog showing how to stage some of the changes and commit them

I have staged only part of the file

I try to remember to copy the commit message before doing the git reset command, so that I can paste it back in when making the commit. Though obviously things have changed now, so it’s worth making sure the commit message is still accurate.

After making the first commit, I can open the commit dialog again to create the second commit:

Commit dialog showing how to stage and commit the rest of the changes

I then continue the rebase and end up with the one commit split into two:

Commit graph showing two commits instead of earlier one

Fuck up, abort and try again

This one is great, and not only does it apply to rebase, it applies to all of Git in general. I love how git has enabled me to just do whatever I want in the code base, with absolutely no fear of fucking things up beyond repair. Like making changes to just try something out, or making some changes to help me debug a particular complex issue we’re having. Things that you do one day, and the next day you forget about. These changes will just light up when going through the commit dialog, and it’s so incredibly easy to reset and get back to normal.

But back to the topic that is aborting a rebase. It’s something I do quite often really, when I maybe get a bit too confident in my interactive rebase skills. Like when I try to move, squash, edit and splitting commits all at once in the same rebase process.

Sometimes it starts out well enough, with a couple of easily solved merge conflicts and things are looking good. But suddenly there’s something that’s not quite right. Maybe I don’t understand why some code is present in a particular merge conflict, and things quickly gets out of hand from there.

Whenever I feel I’ve lost control, it’s usually good to hit that abort button:

Git Extensions rebase dialog showing the Abort button

“Don’t be ashamed to turn around” as we say in Norway.

Of course, that doesn’t solve my conflicts at all. But it lets me try again, and this time I’m not going to do it all at once. Instead I’ll do the rebase process multiple times with a smaller workload each time. Starting with the operations I believe to be easy.

Usually it’s the moving of commits that causes the headaches. I might have been trying to move a commit back in time, when it contained code that wasn’t supposed to have been introduced yet. With Git and rebase, there’s always that time aspect to consider.

While I’m busy working my Git Rebase Magic, other developers are producing code. The next part is about syncing with what has happened.