- Welcome to Trundlebits🐞/
- Blog Posts/
- Rewriting Git History to Change the Author and Email of Commits/
Rewriting Git History to Change the Author and Email of Commits
Table of Contents
Sometimes you are in such a rush to start a new project that you forget to set the correct name and email in your brand new Git repo. By the time you notice, every commit you have made carries the wrong identity.
As long as you have not shared your project with other users, there are tools that you can use to rewrite all of the Git commits so that they use the name and email that you want to present to the world.
You should consider commits pushed to a public repo as “final”. Changing public commits is possible, but usually not very practical, so think twice before you push.
Every Commit Has an Author and a Committer #
Git records two identities on every commit, and each one is a name and email pair:
- the author, the person who originally wrote the change
- the committer, the person who last applied it to the repo
Most of the time these are the same person, and Git shows you only the author, so it is easy to forget the committer exists. They come apart whenever you apply someone else’s patch, cherry-pick, rebase, or accept a squash-merge from a web UI.
This matters here for two reasons: a rewrite needs to fix both identities to be complete, and checking your work with a plain git log will only show you “half of the picture”.
Rewriting Git History with ‘git filter-repo’ #
The instructions below show how to use git filter-repo to change the name and email recorded inside your commits. It does not change your GitHub or GitLab account name; that is a separate thing that you change in your GitHub/GitLab/etc. account settings.
Probably the easiest and safest way to rewrite the history of commits in your Git repo is to use the git filter-repo subcommand.
The filter-repo Git subcommand is a package that is separate from the base Git installation on your system.
The upstream git-filter-repo project is available on GitHub for you to clone and modify. There is also a documentation manual page that talks about why the tool was created, why have users make a fresh clone of a repo before modifying it, notes on removing sensitive data, and comparisons with other tools that perform similar functions.
A Lighter Weight Option: Commit a .mailmap File To Your Repo Instead #
If your commits are already public, or you simply do not want to rewrite anything, Git has a lighter option that costs one commit and breaks nothing.
You can add a .mailmap file to your repo, and Git will use this file when displaying the user names and emails of authors and committers to this repo when viewing the repo logs.
You can find out more about setting up a “repo-level” .mailmap file on the How (and why!) to use a .mailmap file in Git page.
tl;dr git filter-repo Instructions #
Install git-filter-repo with your package manager, then:
# 1. Write the mapping file, OUTSIDE the repo
cat > ${HOME}/mailmap.txt <<'EOF'
New Name <[email protected]> Old Name <[email protected]>
EOF
# 2. Clone fresh from your existing local repo; --no-local matters here; see below;
git clone --no-local /path/to/your/repo repo-to-rewrite
cd repo-to-rewrite
# 3. Rewrite every commit, on every branch and tag
git filter-repo --mailmap ${HOME}/mailmap.txt
# 4. Don't make the same mistake again
git config user.name "New Name"
git config user.email "[email protected]"
# 5. Confirm nothing was missed (should list only the new identity)
git log --all --format='%an <%ae> | %cn <%ce>' | sort -u
# 6. Push to a NEW repo, branches and tags included
git remote add origin [email protected]:/path/to/new/repo.git
git push --all origin
git push --tags origin
The rest of this post explains each of those steps.
Install the git-filter-repo subcommand from package #
Using your system’s package manager, install the git-filter-repo package. For Homebrew and Debian/Ubuntu systems, the package name is called git-filter-repo.
Create a fresh clone of your repo #
Because rewriting history is irreversible, filter-repo will refuse to run unless the repo you point it at looks like a fresh clone. It cannot actually check whether you have a backup, so it checks about a dozen things that are always true of a brand new clone (no stash, no extra remotes, a clean working tree, an untouched reflog, and so on) and uses that as a stand-in for the question it really wants to ask.
Since you already have your code in a repo, creating a new clone is quick and easy, and it leaves you with a backup in hand in case something goes wrong.
git clone --no-local /path/to/your/repo repo-to-rewrite
When you clone a repo that lives on the same filesystem, Git makes the copy cheap by hardlinking the object files instead of copying them. Your “backup” then shares its objects with the original, which is not much of a backup at all. Passing --no-local forces Git to make real copies.
The git-filter-repo manual makes this point directly: when cloning repos on a local filesystem, it is better to pass --no-local to git clone than to pass --force to git-filter-repo.
There is a --force option to git-filter-repo that skips the fresh clone check entirely. Reach for --no-local first. The --force option does not fix the reason the check fired, it just silences the alarm.
Set up the mailmap.txt file #
A mailmap file is a mapping from the name and/or email in your existing commits to the name and/or email you want instead.
Create a new file called mailmap.txt in a directory outside of your repo, open it in a text editor, and add one line per mapping. Blank lines are ignored and # starts a comment.
There are four line formats, and picking the wrong one is the easiest mistake to make here, because each one changes a different amount:
| Format | What it changes |
|---|---|
New Name <[email protected]> |
the name only, on commits with that email |
<[email protected]> <[email protected]> |
the email only, leaving names untouched |
New Name <[email protected]> <[email protected]> |
both, on any commit with that email |
New Name <[email protected]> Old Name <[email protected]> |
both, only when the name and email both match |
New Name <[email protected]> <[email protected]> looks like it only touches the email, but it rewrites the name as well on every commit with that address. If you truly want to change nothing but the email, use the second form, which has no name in front of it at all.
Names and emails are matched case-insensitively. If several old addresses need to collapse into one new identity, write one line for each old address.
Run git filter-repo #
Once your mailmap.txt file is ready, run git filter-repo with the --mailmap switch:
cd /path/to/your/fresh/clone
git filter-repo --mailmap /path/to/your/mailmap.txt
This rewrites every branch and every tag in the repo, and it fixes the author and the committer on each commit.
Rewriting a commit changes its hash, so any cryptographic signature on that commit can no longer verify. git filter-repo deals with this by removing signatures outright, and signed tags are downgraded to plain annotated tags.
If you sign your commits with git commit -S, every “Verified” badge in the repo will be gone, and there is no way to get the old signatures back. You will need to decide whether the rewrite is worth it in this case.
Fix your Git config #
This is the step people skip (or don’t know about), and skipping it means the problem comes straight back.
Rewriting history fixes the commits you already made. It does nothing about the setting that produced them, and your fresh clone inherited that same setting. The very next commit you make will have the wrong identity again.
cd /path/to/your/fresh/clone
git config user.name "New Name"
git config user.email "[email protected]"
That sets the identity for this repo only. If the wrong value came from your global config, which is the usual cause, fix it there too:
git config --global user.name "New Name"
git config --global user.email "[email protected]"
If you juggle work and personal identities on the same computer, a good habit to get in to is to NOT set a global user.email at all.
Git will then refuse to commit until you set one per repo, which turns a previously silent mistake into an error message which prevents commits with an old name from being added to a repo.
Check your repo after making changes #
Reading git log by eye does not scale past a few dozen commits, and the default format shows you the author but not the committer. Ask Git for exactly what you want to check instead:
git log --all --format='%an <%ae> | %cn <%ce>' | sort -u
That prints every distinct author/committer pair in the whole repo, on every branch and tag. The list should be short, and it should contain only identities you meant to keep. Anything left over is a mapping you missed, so add it to mailmap.txt, throw the clone away, and start again from a new clone.
Push your rewritten commits #
git filter-repo removes the origin remote from your fresh clone, on purpose. Before you can push anything, you have to add a new remote repo yourself, and that extra step is the point: it is your chance to push somewhere new rather than back over the original.
git remote add origin [email protected]:/path/to/new/repo.git
git push --all origin
git push --tags origin
A plain git push sends only the current branch, and no tags at all. Since filter-repo rewrote everything, a plain push leaves you with a shiny new repo that is quietly missing your other branches and every tag you ever made.
It is highly suggested to push the rewritten commits to a new repo, and to archive the existing repo for deletion at a later date.
Why not just force push over the old repo? A force push by itself does not duplicate anything; it moves the branch to point at your new commits. The trouble is what everything else still points at:
- Anyone who cloned before the rewrite still has the old commits. The moment one of them runs
git pull && git push, the old history gets merged back in, and now the repo has two copies of every commit, the original and the rewritten one. - The same thing happens if someone reopens a stale pull request from before the rewrite and merges it.
- On services like GitHub or GitLab, the old commits stay reachable by their SHA until the host garbage-collects them, which may be never. Forks and pull request refs keep their own copies.
That last point is worth dwelling on if your reason for rewriting was to remove something sensitive.
Treat the secret as leaked and rotate it.
Pushing to a new repo and telling people to clone that one sidesteps all of this.
Rewriting Git History with ‘git filter-branch’ (Outdated, Not Recommended) #
For rewriting Git history, you may have also seen recommendations to use git filter-branch; for example, the history rewriting example in the Pro Git book still uses git filter-branch.
Even the manpage for git-filter-branch recommends using git-filter-repo instead, as git filter-branch is outdated, and has numerous documented problems.
‘git filter-branch’ is Outdated, But If You Insist… #
If you still insist on using git filter-branch, you can find a script that was published on the GitHub Help pages (archive.org link). The archive.org version of that page is linked, because it was removed from the web at some point.
The actual script used, from this Gist:
#!/bin/sh
# WARNING!!!
# DO NOT USE THIS SCRIPT
# `git filter-branch` is considered broken and unusable
# The contents of this script are shown here so you recognize it
# if you see it posted in other places (and you will)
git filter-branch --env-filter '
OLD_EMAIL="[email protected]"
CORRECT_NAME="Your Correct Name"
CORRECT_EMAIL="[email protected]"
if [ "$GIT_COMMITTER_EMAIL" = "$OLD_EMAIL" ]
then
export GIT_COMMITTER_NAME="$CORRECT_NAME"
export GIT_COMMITTER_EMAIL="$CORRECT_EMAIL"
fi
if [ "$GIT_AUTHOR_EMAIL" = "$OLD_EMAIL" ]
then
export GIT_AUTHOR_NAME="$CORRECT_NAME"
export GIT_AUTHOR_EMAIL="$CORRECT_EMAIL"
fi
' --tag-name-filter cat -- --branches --tags
Those two nearly identical if blocks are the author and committer distinction from the top of this post, spelled out by hand. A mailmap file covers both for you in a single line.
Again, not recommended, but here for information only.
Summary #
If you want a Git repo to use a different name and email than the one its commits were made with, you have two options, and they are not the same kind of fix.
Rewriting with git filter-repo --mailmap actually changes the commits. Work in a fresh clone made with --no-local, pick the right one of the four mailmap line formats, and remember that the form with a name in front of a bare old email changes the name too. Rewriting strips commit and tag signatures permanently.
Instead of using git filter-repo to change the contents of your repo, you can use a .mailmap to change how the identities are displayed. It is the option that still works after your commits are public, and it is the one to reach for when a rewrite is off the table. See the How (and why!) to use a .mailmap file in Git page for full details including instructions.
After a rewrite, set user.name and user.email in the repo, otherwise your next commit recreates the problem you just spent all that effort fixing.
Verify with git log --all --format='%an <%ae> | %cn <%ce>' | sort -u, which covers both identities across every branch and tag.
Push the rewritten commits to a new repo with --all and --tags. Force pushing over the original invites old clones and stale pull requests to merge the old history back in, leaving you with two copies of every commit, and on a public forge the old commits stay fetchable by SHA regardless.
Once you push to a public server it is very hard to undo, so think twice before calling git push.
Don’t use the git filter-branch command, it has documented safety and performance issues that make it a bad choice for rewriting commits in your repo.
Enjoy!
The commands shown were tested against git version 2.55.0 and git-filter-repo version 2.47.0.
AI Content Disclosure
This page was mostly written by a human. Claude Opus reviewed it for spelling, grammar, and technical accuracy, and contributed revisions and additional sections in response to that review.