Changing author info in existing Git commits

If you are not setting the author info correctly or just want to use another email to prevent it from being exposed, you may want to change the author name and/or email of the existing commits. In the post, you will learn how to do that.

Take care to do that if you have pushed these commits to the remote server and there are other members who have access to that repository. Because the other members may have did some work based on your commits.

Change author info of the last commit

If you only want to change author info of the last commit, you can amend it to use new name and email.

# Change the last commit to use new author and email.

# Use --author option to specify the new author and email.
$ git commit --amend --no-edit --author "john <john@noreply.email.com>"

# Or
# Update the user info in configureation first, then amend the commit to
# use the new configured info.

$ git config user.name john
$ git config user.email john@noreply.email.0
# --reset-author, use the current user info.
$ git commit --amend --no-edit --reset-author

Note:

What git commit --amend does is replacing the tip of the current branch by creating a new commit., therefore it renew the commit date as well.

Change author info of multiple existing commits

To change the name and/or email of some specific existing commits, you can use rebase in an interactive mode which allows you to change the commit one by one. Then in the process you can use git commit --amend used above to do the change. See Git book: rewrite history for more details. Below is an example (for messy history, don’t use this method for inconsistent issue that may cause):

# Change the last two commits.
$ git rebase -i HEAD~2
# Action: change "pick" to "edit" in the prompted script for commits
# that you want to change
$ git commit --amend --no-edit --author "john "
$ git rebase --continue
$ git commit --amend --no-edit --author "john "
$ git rebase --continue

To change the name and/or email of the entire history, of course filter-branch can do that as GitHub: changing author info mentioned. In that guide it provides a script that use filter-branch command to rewrite the name and email if the commit email equals some value. However there are issues with filter-branch, there have been some alternatives to replace this command such as git-filter-repo.

If you run the script mentioned in GitHub: changing author info, the Git bash (v2.28.0) will give below warning:

$ ./change-author.sh
WARNING: git-filter-branch has a glut of gotchas generating mangled history
         rewrites.  Hit Ctrl-C before proceeding to abort, then use an
         alternative filtering tool such as 'git filter-repo'
         (https://github.com/newren/git-filter-repo/) instead.  See the
         filter-branch manual page for more details; to squelch this warning,
         set FILTER_BRANCH_SQUELCH_WARNING=1.
Proceeding with filter-branch...

Cannot rewrite branches: You have unstaged changes.

Here we use git-filter-repo command rather than filter-branch according the suggested way in above warning. Before you start, make your working directory clean.

First download git-filter-repo command (it requires Python3) and put the git-filter-repo file to a location on the PATH.

Then write a mailmap file in which you specify the name and/or email. Here we name it as my-mailmap and put below content that specifies to replace both the name and the email of a commit matching the specified commit email address:

john <john@noreply.email.com> <john@gmail.com>

Finally, run filter-repo with the mailmap you created:

$ git filter-repo --mailmap my-mailmap

About mailmap file

The --mailmap option needs a mailmap file of format accepted by git shortlog. Other forms of mailfile:

Replace name of a commit matching the specified commit email address:

Proper Name 

Replace only the email part of a commit matching the specified commit email address:

 

Replace both the name and the email of a commit matching the specified commit email address:

Proper Name  

Replace both the name and the email of a commit matching both the specified commit name and email address:

Proper Name  Commit Name 

Troubleshooting

# If your repository are not a fresh cloned one, use --force
$ git filter-repo --mailmap my-mailmap
Aborting: Refusing to destructively overwrite repo history since
this does not look like a fresh clone.
  (expected at most one entry in the reflog for HEAD)
Please operate on a fresh clone instead.  If you want to proceed
anyway, use --force.

# If it prompts below error, change "python3" to "python" in the git-filter-repo file
$ git filter-repo --mailmap my-mailmap
/usr/bin/env: ‘python3’: No such file or directory

Resources

git-filter-branch

  • Git book: rewriting history

  • GitHub: changing author info

    If you insist to use filter-branch command, run the script like below and wait a few seconds. Then the script will be executed if you don’t interrupt it with ctrl+c.

    $ ./change-author
    WARNING: git-filter-branch has a glut of gotchas generating mangled history
           rewrites.  Hit Ctrl-C before proceeding to abort, then use an
           alternative filtering tool such as 'git filter-repo'
           (https://github.com/newren/git-filter-repo/) instead.  See the
           filter-branch manual page for more details; to squelch this warning,
           set FILTER_BRANCH_SQUELCH_WARNING=1.
    Proceeding with filter-branch...
    
    Rewrite 6f5fae28ee6591861813c4a428379c752217dffe (1/1) (0 seconds passed, remaining 0 predicted)
    Ref 'refs/heads/master' was rewritten
    
    

git-filter-repo

  • git-filter-repo user manual

    Filtering of names & emails

    --mailmap

    ​ Use specified mailmap file (see git-shortlog(1) for details on the format) when rewriting author, committer, and tagger names and emails. If the specified file is part of git history, historical versions of the file will be ignored; only the current contents are consulted.

    --use-mailmap

    ​ Same as: –mailmap .mailmap

  • git-shortlog(1)

Changing the last commit in Git

Sometimes, you may want to change the last commit you’ve made. --amend allows you do that. With that option, you can replace the last commit with a new one. But take care to do that if you’ve pushed it to the remote and there are other members in the repository. In such case, they may have made changes based on your last commit, then remember to notice them to execute git pull --rebase to avoid troubles after you pushed the new commit .

A more complicated case is before you amend your commit, someone have pushed their commits based on your last commit. In such a case, a simple way is to execute git pull to get the new changes, and make a new commit in which changes of your last commit can be reverted with git revert command.

# Pull new changes that have been pushed after your last commit
$ git pull

# Revert your last commit. Note you may need to fix some conflicts if they occur.
# If nothing is wrong, a new commit will be generated automatically.
# There will be a chance to write the commit message, or you can leave it as it was.
# Examples: 
# git revert 6d84d27
# git revert HEAD~~
# git revert HEAD~3
$ git revert 

# Push
$ git push

A complicated method is to execute rebase command to make the subsequent commits being based on the changed new one. It is not discussed here.

Process of changing the last commit

Below is a full process to make sure every thing will be fine in various situations.

# Step 1
# Make sure you are at the tip of the branch (the others have not pushed new commits 
# based on your last one), notice the other members not to push during the process.

# Check whether you are ahead or behind the remote branch
$ git branch --vv

# If you are not behind the remote branch, go to the next step.
# If you are behind, see the second paragraph part of the article.

# Step 2
# Make modifications in the working directory

# Step 3
# Add the modifications
$ git add functions.php

# Step 4
# Recommit with --amend option
# --amend, replace the tip of the current barnch by creating a new commit
$ git commit --amend -m 'new commit message'

# Step 5
# If you have not pushed the last commit.
$ git push
# If the last commit has been pushed to remote, repush it with --force.
# --force, force to push to allow overwriting the commits in the remote.
# Use --force with care.
$ git push --force

# Step 6
# Notice others to run "git pull --reabase" if there are other members.

If you want to change the commit for specific needs, such as only changing the commit message or keeping the old commit message, see below.

1. Change the last commit without changing commit message

# Amend commit without changing commit message
# --no-edit, use the old commit message without lauching an editor to change it
$ git commit --amend --no-edit

2. Change only the commit message in the last commit

# Change the commit message
$ git commit --amend -m 'new commit message'

3. Change only the author info in the last commit

Below is an example to change the author to be “john” and the email to be “john@example.com”. Remember to wrap the email with “ in the first command.

# Change author info in the last commit.
# the new author is john, and email is john@example.com
# --author="<Author <Author email>"
$ git commit --amend --no-edit --author="john <john@example.com>"

# Or
# First set the author and email for the repo
# and then change the commit with the new configured author and email.
# --reset-author, renew the author infor in the commit with the configured user info.
$ git config user.name john
$ git config user.email john@example.com
$ git commit --amend --no-edit --reset-author

Resources

git commit

Deleting files from Git commit history

If you commits an sensitive file or a huge unwanted file by accident, you may want to remove it from every commit. Because there are issues with filter-branch , here we will use its alternative and the recommend filter-repo command to do that. And git filter-repo is much more easier to use than filter-branch.

About stopping using filter-branch

Git has recommended you to use filter-repo to replace filter-branch when you run git filter-branch:

         git-filter-branch has a glut of gotchas generating mangled history
         rewrites.  Hit Ctrl-C before proceeding to abort, then use an
         alternative filtering tool such as 'git filter-repo'
         (https://github.com/newren/git-filter-repo/) instead.  See the
         filter-branch manual page for more details; to squelch this warning,
         set FILTER_BRANCH_SQUELCH_WARNING=1.

Delete files and/or folders from commit history

Using filter-repo to delete files or folders from the commit history is very easy, just pass it with --path to specify the paths to files and/or folders to delete and --invert-paths. Be care to take such actions for it would make changes that may can not be recovered. You can make a copy of your repository first before you run filter-repo.

Below is an example:

# Delete build folder and test.txt file from all commits
# You can specify folder with or without the trailing slash.
$ git filter-repo --path build/ --path test.txt  --invert-paths
Parsed 7 commits
New history written in 4.04 seconds; now repacking/cleaning...
Repacking your repo and cleaning out old unneeded objects
HEAD is now at 90c767d delete c.txt from lib of inc
Enumerating objects: 70, done.
Counting objects: 100% (70/70), done.
Delta compression using up to 8 threads
Compressing objects: 100% (55/55), done.
Writing objects: 100% (70/70), done.
Total 70 (delta 18), reused 0 (delta 0), pack-reused 0
Completely finished after 5.64 seconds.

Note:

If your repository is not a fresh clone, you also need to pass it --force option to force the execution, otherwise it prompts below :

$ git filter-repo --path build/ --path test.txt  --invert-paths
Aborting: Refusing to destructively overwrite repo history since
this does not look like a fresh clone.
  (expected at most one entry in the reflog for HEAD)
Please operate on a fresh clone instead.  If you want to proceed
anyway, use --force.

Without --invert-paths, it will only keep the files or folders you pass it with --path:

# Keep only src/ folder and readme.txt but delete others
$ git filter-repo --path src/ --path readme.txt

You can even use two commands to use both the inclusion mode and exclusion mode (with --invert-path).

# Keep the src/ folder and readme.md but exclude files under src/ named data
# --path-glob, glob of paths to include in filtered history.
#  (*) will match across multiple directories under src/ folder.
git filter-repo --path src/ --path readme.md
git filter-repo --path-glob 'src/*/data' --invert-paths

~~Not recommend: delete a huge folder from each commit with git filter-branch~~

git filter-branch executes the specified command for each commit specified by you and generates new commits.

Before you start, you must keep it in mind that this operation changes the existing history. If it is a public repository and someone have did some work based on the commits you want to rewrite, you’d better not do this. If you have to, remember to notify them to run git pull --rebase command.

Here is an example of how to remove a huge folder from each commit which is committed accidentally at first.

You’d better test below commands in a temporary repository to make sure that they work properly for your git version. It is met that git filter-branch below removes the folder in working tree as well but it should not.

We do it in a new testing branch, when the result is what we want then reset it as the prior branch.

# Do it in a new testing branch
$ git checkout -b test

# Remove 'build' folder from every commit on the new branch
# --index-filter, rewrite index without checking out
# -r, remove recursively in subfolders
# --cached, remove it from index but not include working tree
# --ignore-unmatch, ignore if files to be removed are absent in a commit
# --prune-empty, remove empty commits generated by 'git rm' command
# HEAD, execute the specified command for each commit reached from HEAD by parent link
$ git filter-branch --index-filter 'git rm -r --cached --ignore-unmatch build' --prune-empty HEAD
Rewrite fee4b8ee9df321a877cd2663b20b293eea4a1f8c (1/2)rm 'build/main.app'
Rewrite 63f272ab5152c66693614efae77567799837c6e0 (2/2)
Ref 'refs/heads/test-filter' was rewritten

# The output is OK, reset it to the prior branch master
$ git checkout master
$ git reset --soft test

# Remove test branch
$ git branch -rm test

# Push it with force
$ git push --force origin master

If you changed commits in remote repository, remember notice other members execute below command:

# Tell others to execute below command if you changed commits in remote repository.
$ git pull --rebase

Note:

  1. If --ignore-unmatch option is not added, it will fail when the files to be removed do not exist in the commit.
  2. The files you removed will stay in disk for a while, they will be removed entirely in the next automatic garbage collection of git.

Tips: To avoid adding unwanted files by accident, you should ignore it.

Other useful options:

# Execute the specified command for the last 5 commit
$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch user.pem' HEAD~6..HEAD

# Execute the specified command for all branches
$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch user.pem' -- --all

# Update tags when executing filter-branch, remember to push them to remotes afterwards
$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch user.pem' --tag-name-filter cat HEAD

# Remove empty commits generated by 'git rm' command
$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch user.pem' --prune-empty HEAD

Resources

git filter-repo

git filter-branch

  • git-filter-branch command

    Warning

    git filter-branch has a plethora of pitfalls that can produce non-obvious manglings of the intended history rewrite (and can leave you with little time to investigate such problems since it has such abysmal performance). These safety and performance issues cannot be backward compatibly fixed and as such, its use is not recommended. Please use an alternative history filtering tool such as git filter-repo. If you still need to use git filter-branch, please carefully read SAFETY (and PERFORMANCE) to learn about the land mines of filter-branch, and then vigilantly avoid as many of the hazards listed there as reasonably possible.

  • github: removing sensitive data from history

  • git book: rewriting history

Committing only a commit message in Git

Commit only a commit message in Git. Last updated: 2018-12-16

Usually, a commit can not be empty, but you can use --allow-empty option to do this:

# Commit only a commit message.
$ git commit --allow-empty -m 'my commit message'