271

I've committed a bunch of commits to a project on Github, however I realized I hadn't set up the proper email and committer full name on the computer I'm currently using to make my commits and therefore the users avatar and email address are not there.

How can I rewrite all past commit email and usernames?

JP Silvashy
  • 44,746
  • 48
  • 146
  • 219
  • 12
    possible duplicate of [How do I change the author of a commit in git?](http://stackoverflow.com/questions/750172/how-do-i-change-the-author-of-a-commit-in-git) – givanse Jan 08 '14 at 18:40
  • 1
    I experienced this after changing the email address on my GitHub account. In addition to pushing code changes from the local git repo using the git command line (and not the GitHub desktop) interface, I also edited text and managed files directly from the remote git repo using the GitHub web interface. The new email address propagated only to the commits resulting from the latter actions and not the former. – Bob Aug 18 '18 at 16:41

9 Answers9

341

You can add this alias:

git config --global alias.change-commits '!'"f() { VAR=\$1; OLD=\$2; NEW=\$3; shift 3; git filter-branch --env-filter \"if [[ \\\"\$\`echo \$VAR\`\\\" = '\$OLD' ]]; then export \$VAR='\$NEW'; fi\" \$@; }; f"

To change the author name:

git change-commits GIT_AUTHOR_NAME "old name" "new name"

or the email for only the last 10 commits:

git change-commits GIT_AUTHOR_EMAIL "old@email.com" "new@email.com" HEAD~10..HEAD

Alias:

change-commits="!f() { VAR=$1; OLD=$2; NEW=$3; shift 3; git filter-branch --env-filter \"if [[ \\\"$`echo $VAR`\\\" = '$OLD' ]]; then export $VAR='$NEW'; fi\" \$@; }; f"

Source: https://github.com/brauliobo/gitconfig/blob/master/configs/.gitconfig

1ace
  • 5,211
  • 4
  • 23
  • 29
brauliobo
  • 5,204
  • 4
  • 28
  • 33
  • 14
    Also `git change-commits GIT_COMMITTER_EMAIL "old@example.com" "new@example.com"` to change the committer email. – laurent Jan 31 '17 at 21:32
  • 26
    fixed for "eval: [[: not found" on ubuntu and add a confirm `change-commits = "!f() { VAR1=$1; VAR='$'$1; OLD=$2; NEW=$3; echo \"Are you sure for replace $VAR $OLD => $NEW ?(Y/N)\";read OK;if [ \"$OK\" = 'Y' ] ; then shift 3; git filter-branch --env-filter \"if [ \\\"${VAR}\\\" = '$OLD' ]; then export $VAR1='$NEW';echo 'to $NEW'; fi\" $@; fi;}; f "` – qxo Apr 08 '17 at 04:37
  • 17
    git: 'change-commits' is not a git command. See 'git --help'. Means you have not added the alias to your git config. e.g. git config -e – Wayne Aug 20 '17 at 12:49
  • 5
    This just made duplicates of all the commits with the email I wanted to change. Doesn't appear to rewrite history. @Olivier Verdier's solution worked for me. – Jake Wilson Nov 21 '17 at 16:10
  • Why do you use a `!` in the beginning of the alias? What does that do? – HelloGoodbye Feb 16 '19 at 11:49
  • Also, the `change-commits =` expression in your answer differs by one sign (a ``\`` before `$@`) from the code in your GitHub repository. This is a mistake, right? – HelloGoodbye Feb 16 '19 at 12:06
  • 1
    Git also creates a backup. I've used this to change the `GIT_COMMITTER_EMAIL`, which worked. However, when I try to use it again to change the `GIT_AUTHOR_EMAIL`, Git complains and says `Cannot create a new backup.` `A previous backup already exists in refs/original/` `Force overwriting the backup with -f`. Now, I can use the `-f` flag, or I can remove the backup using [`git update-ref -d refs/original/refs/heads/`](https://stackoverflow.com/a/34177510/1070480). – HelloGoodbye Feb 16 '19 at 13:06
  • 1
    Can this alias be modified to support regex matches so that multiple old variable values can be matched? For example if I have used many different email addresses for the various commits and I would like to change all of them to a new one? – HelloGoodbye Feb 16 '19 at 13:08
  • 2
    I ended up just using `git checkout -b temp-branch` followed by `for var in GIT_AUTHOR_EMAIL GIT_COMMITTER_EMAIL; do for email in email@address1 email@address2 email@address3; do git change-commits $var $email target-email@address; git update-ref -d refs/original/refs/heads/temp-branch; done; done` to change all email addresses both for author and committer. – HelloGoodbye Feb 16 '19 at 13:42
  • When doing `git config alias.change-commits`..., don't forget to add `--global` to make this a global, rather than project, alias. – Gabriel Staples Mar 25 '19 at 08:10
  • 10
    Doing it twice in a row with different inputs leads to: `Cannot create a new backup. A previous backup already exists in refs/original/` – theonlygusti Apr 10 '19 at 16:11
  • 1
    My solution to `Cannot create a new backup....` issue is from https://blog.tinned-software.net/rewrite-author-of-entire-git-repository/ : `$ git fetch origin $ git reset --hard origin/master $ git for-each-ref --format='delete %(refname)' refs/original | git update-ref --stdin $ git reflog expire --expire=now --all $ git gc --prune=now` snippet: https://gist.github.com/gwpl/dbf993223c8433767c81925513bef656 – Grzegorz Wierzowiecki Dec 08 '19 at 09:39
  • 3
    When I do this in Windows 7 PowerShell, I get nasty errors about unrecognized commands `then` and `fi\" \$@; }; f "`. Seems to work ok in Git for Windows Bash. – Benargee Feb 02 '20 at 19:35
  • A word of warning: Y/N is case sensitive – farlee2121 Jul 08 '20 at 18:59
  • Your git backups are just files that you can delete that exist in `.git/refs/original`. – Mike Odie Jul 22 '20 at 13:58
  • `git: 'change-commits' is not a git command. See 'git --help'.` – basickarl Sep 21 '20 at 13:31
  • @qxo ’s solution using a command line instead of .gitconfig: `git config --global alias.change-commits '!'"f() { VAR1=\$1; VAR='\$'\$1; OLD=\$2; NEW=\$3; echo \"Are you sure for replace \$VAR \$OLD => \$NEW ?(Y/N)\";read OK;if [ \"\$OK\" = 'Y' ] ; then shift 3; git filter-branch --env-filter \"if [ \\\"\${VAR}\\\" = '\$OLD' ]; then export \$VAR1='\$NEW';echo 'to \$NEW'; fi\" \$@; fi;}; f "` – Matthieu Jan 04 '21 at 11:13
  • I am getting `change-commits is not a git command` – Nitish770 Jun 02 '21 at 11:17
  • As @HelloGoobye I am also wondering: Why do you use a ! in the beginning of the alias? Besides that question, thank you so much! – tbrodbeck Dec 02 '21 at 19:40
137

See here:

git filter-branch -f --env-filter \
"GIT_AUTHOR_NAME='Newname'; GIT_AUTHOR_EMAIL='newemail'; \
GIT_COMMITTER_NAME='committed-name'; GIT_COMMITTER_EMAIL='committed-email';" HEAD
spongebob
  • 8,552
  • 13
  • 45
  • 80
Olivier Verdier
  • 44,254
  • 26
  • 97
  • 90
76

If you have already pushed some of your commits to the public repository, you do not want to do this, or it would make an alternate version of the master's history that others may have used. "Don't cross the streams... It would be bad..."

That said, if it is only the commits you have made to your local repository, then by all means fix this before you push up to the server. You can use the git filter-branch command with the --commit-filter option, so it only edits commits which match your incorrect info, like this:

git filter-branch --commit-filter '
      if [ "$GIT_AUTHOR_EMAIL" = "wrong_email@wrong_host.local" ];
      then
              GIT_AUTHOR_NAME="Your Name Here (In Lights)";
              GIT_AUTHOR_EMAIL="correct_email@correct_host.com";
              git commit-tree "$@";
      else
              git commit-tree "$@";
      fi' HEAD
ewall
  • 25,753
  • 15
  • 65
  • 84
  • 7
    This works perfect whereas the answer marked in green didn't – jmary Dec 12 '18 at 10:52
  • 1
    Afterwards, one might want to clear the backup with `git update-ref -d refs/original/refs/heads/master`, see . – cknoll Mar 23 '20 at 11:48
  • FYI: If you have multiple incorrect names / emails you may need to run this multiple times. If that happens it will moan at you with this error: `A previous backup already exists in refs/original/` In that case, re run it, with the new email, and add a `-f` before the --commit-filter. Use at your own discretion. Usually `-f` is a dangerous thing to do without knowledge of what it's doing. – Chuck Apr 21 '20 at 17:01
  • Now I got fatal: refusing to merge unrelated histories – pguardiario Jan 24 '21 at 00:12
  • @pguardiario The error you're seeing could be due to a Git v2.9 change to the default merge behavior... maybe look into the `--allow-unrelated-histories` option? – ewall Jan 25 '21 at 17:56
  • what do you mean by "In Lights"? – dedede Mar 27 '21 at 16:29
  • @dedede Just a joke... like a ["see your name in lights" marquee](https://www.google.com/search?tbm=isch&q=your+name+in+lights). – ewall Mar 28 '21 at 19:07
  • This answer is perfectly fine while the one having more votes are not fully correct – Vinkal Vishnoi Apr 02 '21 at 09:37
  • This works perfectly and in about ten seconds so bravo, but jokes in answers about git just don't work because people on Stack Overflow researching git are typically in a state of panic. – 1252748 Jun 20 '21 at 22:44
  • I tried this, but now all the commits are double. How I can revert it back? – Alex Bogias May 12 '22 at 15:11
31

After applying Olivier Verdier's answer:

git filter-branch -f --env-filter \
"GIT_AUTHOR_NAME='Newname'; GIT_AUTHOR_EMAIL='newemail'; \
GIT_COMMITTER_NAME='committed-name'; GIT_COMMITTER_EMAIL='committed-email';" HEAD

...to push the changed history on the original repository use:

git push origin +yourbranch

The above command (note the plus) forces rewriting the history on the original repo as well. Use with caution!

Again, WARNING: this will make ALL commits as committed by Newname/newemail! The scenario is where you have a repo with only one author who committed using different identities by mistake and you want to fix it.

sarusso
  • 504
  • 4
  • 9
24

https://help.github.jp/enterprise/2.11/user/articles/changing-author-info/

#!/bin/sh

git filter-branch --env-filter '

OLD_EMAIL="oldEmail@xxx-MacBook-Pro.local"
CORRECT_NAME="yourName"
CORRECT_EMAIL="yourEmail"

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

this totally worked for me. After git push, make sure to see update on git's web portal. If the commit was still not linked to my account, shown default thumbnail image next to the commit and it was not reflected on my contributions timeline chart, go to the commit url and append .patch at the end of the url, and verify the name and email are correct.

Teaqu
  • 2,705
  • 1
  • 13
  • 21
Jacccck
  • 389
  • 3
  • 6
  • Whilst this may theoretically answer the question, [it would be preferable](//meta.stackoverflow.com/q/8259) to include the essential parts of the answer here, and provide the link for reference. – jhpratt Jan 24 '19 at 22:51
  • 3
    This is the only one rewriting all branches. – Bruno Zell Jan 24 '20 at 13:01
  • this should be the correct answer. the rest is waste of time and possible damage to the repository – acgbox Oct 21 '21 at 13:23
  • This code works and is self-documenting. Tip: make this into a script like `reauthor.py`, `chmod +x` it, and drop it into whatever local repos you have. Make sure all your changes are committed or stashed first, then run it from each affected repo. – Drakes Nov 25 '21 at 05:08
19

Considering use of git-filter-branch is not desired, to do the same thing in git-filter-repo (you may need to install it first with pip install git-filter-repo):

git-filter-repo --name-callback 'return name.replace(b"OldName", b"NewName")' --email-callback 'return email.replace(b"old@email.com", b"new@email.com")'

If repository is original, w/o remote, you will have to add --force to force rewrite. (You may want to create backup of your repo before doing this.)

If you do not want to preserve refs (they will be displayed in branch history of Git GUI), you will have to add --replace-refs delete-no-add.

For more advanced features, see "Filtering of names & emails".

P.S. Stolen and improved from https://stackoverflow.com/a/59591928/714907.

Pugsley
  • 867
  • 12
  • 12
  • Doing it this way also avoids the time wait that the filter-branch warning imposes on at least Git 2.34.1. This answer is pretty nice and deserves more upvotes. – Alejandro González Dec 20 '21 at 21:17
11

For those that just want the easy copy paste version (aside from updating emails and names):

git config alias.change-commits '!'"f() { VAR=\$1; OLD=\$2; NEW=\$3; shift 3; git filter-branch --env-filter \"if [[ \\\"\$\`echo \$VAR\`\\\" = '\$OLD' ]]; then export \$VAR='\$NEW'; fi\" \$@; }; f "
git change-commits GIT_AUTHOR_NAME "<Old Name>" "<New Name>" -f
git change-commits GIT_AUTHOR_EMAIL <old@email.com> <new@email.com> -f
git change-commits GIT_COMMITTER_NAME "<Old Name>" "<New Name>" -f
git change-commits GIT_COMMITTER_EMAIL <old@email.com> <new@email.com> -f
Nick Kuznia
  • 1,628
  • 16
  • 27
3

The answers already present are complete. But are you sure, you need those? For eg. I was facing a similar issue but the answers here were overkill for that case. My case and the solution are described below:

Assume you have two email ids, no_longer_want@gmail.com and want@gmail.com. It is possible that the previous commits were through no_longer_want@gmail.com which is not the email id that you want. In that case, one option is to simply link want@gmail.com to your GitHub account.

How to do it?

Find the option to add the email in the Emails section on the settings page.

enter image description here

Still lost?

The first three steps mentioned on the github page will be enough.

NOTE:

  1. Github supports multiple email ids for a single github account.
  2. Since you are not replacing the email id, but just adding a new one, commits through both the email ids are linked to your Github account.
  3. It's not necessary that want@gmail.com should be set as primary email id.
paradocslover
  • 2,600
  • 3
  • 15
  • 38
3

An alternative to rewriting the history, if you mainly care about the local repo display names, is the .mailmap file, which is basically a list of names and emails. For example, placing this in a .mailmap file in the root of a repo:

Tamika Page <tamika@somejob.com>
Orlando Cervantes <orlando.cervantes@otherjob.com> Orlando Jackson <orlando.jackson@otherjob.com>
Jared Michael <jared.michael@gmail.com> <jared@desktop.(none)>

will result in any commits attributed to tamkia@somejob.com being shown with the name Tamika Page, regardless of the Committer Name, commits attributed to Orlando Jackson <orlando.jackson@otherjob.com> being displayed as Orlando Cervantes, and the pesky <jared@desktop.(none)> commits being attributed to jared.michael@gmail.com. For full details, check out the git documentation for this feature - since it is built into git it should work for any reasonably new git client.

There's a big caveat here though: while it's been supported for quite a while in the official git client, support in various git implementations, notably the big web interfaces, isn't guaranteed - see this question, where consensus on whether GitHub respects it is mixed but seems negative. I threw together quick test repos on GitHub and GitLab and neither seem to pay attention to .mailmap, unfortunately.

As mentioned in @paradocslover's answer, the services have their own interfaces to do similar things, but you have to set that up per-service, and it won't affect your local copy at all. Because .mailmap is part of the repo, it will work locally for anyone who clones your repo (you can clone the repos linked above to see for yourself), but it seems it won't show in the web interfaces I tested.

For some cases you do want to rewrite the history, of course, but that can be rather invasive and comes with all the standard caveats, so for some situations it's nice to have this option, which may be sufficient, especially if rewriting isn't practical.

cincodenada
  • 2,656
  • 21
  • 35
  • I'd ask what level does this work at? I already used a fix above for my issue and when I pushed the repository up to the remote all my changes were made correctly. Does this work in that manner or is this just a local 'view-only/display-only ' fix – LFMekz Aug 02 '21 at 22:14
  • 1
    It is indeed display-only, the actual commit data remains unchanged. Rewriting is a more permanent solution and probably better if it is an option. But rewriting can be very disruptive for some use cases (e.g. if the repo is shared with any significant number of people), so this is an alternative for when rewriting is impossible or impractical. – cincodenada Aug 03 '21 at 05:29
  • 1
    Your question did prompt me to actually check if it is supported by the big web clients, and unfortunately it seems that it is not, which is a shame. They have their own ways of handling this, but it would be nice if they used the existing standard to do so, although I suppose they have concerns around impersonation that are better solved by their own solutions, altho even their own verification only solves a small subset of those concerns. – cincodenada Aug 03 '21 at 05:30