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.
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.
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.
In this example we have "Ticket Number 123" appended to the start of the commits in the form [T123].
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?
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 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 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.
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.
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.
You will notice that one of the options below is
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.
You can see that we have changed the word
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.
Very similarly to our initial rebase file we can now edit the commit message here, replace the incorrect ticket number.
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.
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:
git checkout <your_branch>
git branch -m <new_branch_name>
git push origin -u <new_branch_name>
git push origin --delete <old_branch_name>
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.