Rewriting git commit message history across multiple branches
The usual way to modify a previous git commit is to rebase interactively:
git rebase -i <parent of oldest commit to edit>
This lets you modify the past commit and then it reapplies all commits in the current branch since then on top of the modified commit.
This is all you need if you only have one branch. This time, however, I had multiple branches splitting off after the commit I had to change. When I used the rebase technique, the other branches remained along with the unmodified commit they were based on.
I decided to try a different approach - I used filter-branch
.
filter-branch
is very powerful and has many options. In this case, I
only wanted to rewrite a commit message, so I used the
--msg-filter
option. This pipes each message to a shell
command and replaces it with the output of that command - perfect in
conjunction with sed. This method, unlike rebasing onto a single
edited commit, lets you programmatically edit all commit messages
One of the things I wanted to do was remove the comments that had been added by git-svn. This command did the trick:
git filter-branch -f --msg-filter 'sed "s/git-svn.*$//g"' -- --all
If you have any tags, you should add --tag-name-filter cat
. This
updates the tags to point to the modified commits. It’s more complicated if
the tags are signed - see the git-fliter-branch
man page for
details.
Before this operation, git makes a backup, referred to as the
original. If there is already a backup git will, be default, refuse to
run the command, as doing so would overwrite the existing backup with
a new one. Use -f
to force it.
-- --all
applies the filter to all branches. Alternatively, single
branch names can be given.
The above commands have taken care of modifying our commits but they have not removed the originals, which have been kept as backups. To purge these from the repository, you need to remove all references to them and then run the garbage collector.
Edit these files to delete any lines which refer to the original commits:
.git/info/refs
.git/packed-refs
And do this:
rm -rf .git/refs/original
rm -rf .git/logs/
Now garbage collect:
git gc --prune=now