If you juggle multiple git user names or emails in your job and/or spare time, it's not unlikely that you'll end up committing code with the wrong user. At a glance, there's no easy way to fix this; you can't just change the author of a commit---they're immutable, after all---but you can delete a commit and create a new one. This sounds tedious, and doing it manually is tedious, but fear not, for I am here to show you how we can do it automatically!
The trick is based on the answers to this Stack Overflow question. In short, it's just a matter of rebasing off a specific commit and then automating the process of accepting the commit as-is while changing the author.
The basics: git rebase -i
Interactively rebasing lets you choose a commit to rebase off and then choose what you want to do with all the commits since (edit, pick, delete, etc.). In this case, we want to amend the commits, so change them all to edit
. If you want to leave some commits untouched, just mark them as pick
.
When rebasing interactively, git will stop after each commit we've chosen to edit, saying you can amend the commit by running git commit --amend
. We want to reuse the same commit message, but edit the author, and the trick for that is using the --reset-author
and --no-edit
options.
So far, our process looks like this:
- Set the right user details in our config.
git config user.name <correct user name>
git config user.email <correct user name>
- Initiate the rebase.
git rebase -i <commit hash>
- Change the commit's author and continue the rebase.
# amend the commit, changing the author, but leaving the message intact.
git commit --amend --reset-author --no-edit
git rebase --continue
- Repeat step 3 until you're done
Tedious, but at least it works. Most likely, this doesn't happen often enough that you'll need to automate it, so we could just leave it, but where's the fun in that?
Taking it further
Let's think about what we want to do: For each commit since a specific one, we want to change the author and continue the rebase. Sounds pretty easy, right? We'll just need to figure out how many iterations we need, and we have ourselves a one-liner!
So how do we go about finding out how many commits we need to change? We could count, but git does offer a command we can use for it: git rev-list
. It doesn't do much without any arguments, but with the --count
flag and a commit, we can start working some magic. The --count
functionality, as you might expect, counts the number of commits up until the provided hash by default, but, conveniently, it can also operate on commit ranges! (As an aside: git rev-list
has a lot of interesting uses, so go read the docs if you want to know more!)
To get the number of commits since the one we're using as the rebase root (thus not changing):
git rev-list <rebase root hash>...HEAD
This is starting to look pretty good. If we put something together, it might look something like this, with <root>
being the hash of the commit we want to rebase off:
n=$(git rev-list --count <root>...HEAD);\
git rebase -i <root>;\
for i in $(seq $n);\
do\
git commit --amend --reset-author --no-edit;\
git rebase --continue;\
done
or in Fish for all you cool kids:
set n (git rev-list --count <root>...HEAD);\
git rebase -i <root>;\
for i in (seq $n);\
git commit --amend --reset-author --no-edit;\
git rebase --continue;\
end
Note that this is a pretty naive way to do it. As outlined in this Stack Overflow question, the number of commits you're looking at might not be what's returned by git rev-list
directly. For instance, if you have multiple branches that have been merged in, you might need to add the --first-parent
flag to get the desired behavior.
Special case: what about rebasing off the root of the repo?
Ah, yes; this is what I've actually had to do each time I've been amending authors. We've tackled rebasing off a repo root in a prior post, so we already know that we can pass the --root
flag to git rebase
. When we rebase off the root, we don't need to deal with commit hashes, so it actually becomes a bit simpler:
In bash:
n=$(git rev-list --count HEAD);\
git rebase -i --root;\
for i in $(seq $n);
do\
git commit --amend --reset-author --no-edit;\
git rebase --continue;\
done
And in fish:
set n (git rev-list --count HEAD);\
git rebase -i --root;\
for i in (seq $n);\
git commit --amend --reset-author --no-edit;\
git rebase --continue;\
end