It's not common but sometimes we have uses for rewriting our git history. This post may (or may not) have been inspired by starting at a new job and not wanting my peers to know I completely misread one of my first programming tickets...


Many organisations approach their git commit standards differently. Some have strict adherence to commit message formats, while others are more lax. For traceability of past work it is often common to use a ticket (or some other reference) number in commit messages. That's fine, but what happens if you are new at a company, taking on a little too much information at once, and typo one of your first ticket numbers, only to realise as you go to submit a Pull Request?

You try and fix your mistake of course! Learning is always fun and git is surprisingly hard to break so let's see how we can fix the problem.


The Setup

Let's assume we have a repository that uses trunk based development and some sort of feature branching strategy. We might have something like below.

Initial repo state

Working on a Feature Branch

We start working away while others continue to merge work into our trunk. For brevity I've commited directly to the main branch but I suggest in real world scenarios you have branch policies that prevent this behaviour. As a result we might be in a state similar to below.

Creating a feature branch

In this example we have "Ticket Number 123" appended to the start of the commits in the form [T123].

The Problem

Whoops! We were actually working on "Ticket Number 234", not only do I look silly but we might also break integrations with other work tracking tools, we might want to try and fix this ASAP.

To fix this particular mistake we are going to take advantage of the rebase command. Normally I would suggest that best practice would be to just rebase the branch off your current parent as preserving the commit it branched from isn't super useful. However there might be cases (other than pride) where doing an in-place rebase is useful. So how can we rebase this branch in-place and also re-write all of our commit history?

Rebasing

We first want to find the parent commit for this branch, given we are attempting to do it in-place. There are a few ways to do this and I will show them in my preferred order.

The Merge-Base Command

The merge-base command attempts to find the base commit of the supplied branches. For a feature branching strategy where that should be one commit (and not some hypothetical commit based off a multi-branch merge) this yields the fastest results. In context of our example this is what it looks like.

Using the merge-base tool

Using a Visual Git Tool

Using a visual git tool provides you with a good overview of how your branches look in relation to the rest of the repository and can be easily scanned to find the parent commit. Naturally the longer your branch the harder this might be.

Using Sourcetree

Using Git Log

Much like a visual git tool git log can be a little cumbersome if your feature branch or parent branch are long, however the following command gives us a very similar analogue to our visual tool.

Using the git log tool

Now that we have our commit hash we can start an interactive rebase that not only replays our commits it lets us choose how we want to handle each commit.

Start the rebase command.

Starting a rebase

We are now prompted with the history of the commits on our branch as well as some instructions on what we can do in interactive mode to change our previous commits.

Showing rebase options

You will notice that one of the options below is reword or r for short. We still want to use this commit but we definitely want to change that commit message. Below is what it looks like once we have changed this file. You will not change the commit message here just the command, changing the message comes later.

Showing changed commands

You can see that we have changed the word pick to reword for each commit as they all contain the wrong ticket number. Depending on your text editor the process for this might be different. I am using nano so I can save and close the file.

Once you close the initial rebase file you will be prompted with a new file for each commit on your branch. The longer the branch the longer this will take, sorry.

An example commit

Very similarly to our initial rebase file we can now edit the commit message here, replace the incorrect ticket number.

Rewriting the commit

We have changed [T123] to read [T234]. Save and close the file and repeat the process for every commit.

When done check your git log again and you should see updated commits with the correct messages, also referencing the original commit.

Fixed all commit messages

Assuming this branch is already in an upstream, which for me it was, you will need to force push over this branch to completely rewrite the history using git push -f.

Congratulations! You have now successfully updated all the commit messages on your feature branch.

Branches with Ticket Numbers

In my example my branch name didn't include a ticket number, sometimes they do. In this case you might want to also change the branch name, again if it makes sense. We can perform the following steps:

  1. git checkout <your_branch>
  2. git branch -m <new_branch_name>
  3. git push origin -u <new_branch_name>
  4. git push origin --delete <old_branch_name>
  5. Profit.

A Note on Rewriting History

In general it is not great practice to rewrite history, the git log exists for a reason and rebasing in particular (which rewrites commits) might cause issues if other developers are relying on those commits. If you're working on a public repository, or are generally unsure about how to handle a situation like this, it is better to reach out to your repo owner for advice.

In my case it was a feature branch only I was going to touch and having correct commit messages on the branch was more useful for the team than trying to come up with a different solution that maintained the git history with the wrong commit messages preserved.

The same goes for branches too, I was comfortable to rename and delete the upstream branch for this particular piece of work but other repo owners might want to keep the wrongly named branch stale, or you might not have access to delete branches, so double check before making changes you might not be able to reverse.


Do you have any git success or horror stories? I would love to hear about them on Twitter.