Skip to content

Instantly share code, notes, and snippets.

@rnag
Last active February 27, 2024 19:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rnag/9b9f22bc9ccb3be1833fbc5d1b24a342 to your computer and use it in GitHub Desktop.
Save rnag/9b9f22bc9ccb3be1833fbc5d1b24a342 to your computer and use it in GitHub Desktop.
Git - Change Commit Author

Bash/Zsh Function to Change Author for Commit(s)

Based off my Answer on SO.


The below function cca (shorthand for change-commit-author) makes some assumptions:

  • The branch with commit(s) to update author for is the current branch.
  • Git User / Email (AKA New Author) is the one currently set up with git.
  • One of the following arguments is passed in:
    • Email, to rewrite author of all commits under that email.
    • Space-separated list of Git Commit SHA(s), to change the author of.
  • Git commit signature verification is desired, and SSH/GPG keys are set up for that purpose (I made a script for this in case it is helpful).

Warning

Do not use in the main branch of shared or published projects without confirming with other collaborators beforehand.

The reason is this script performs some "destructive" actions:

  • It rewrites all commit history, so if someone is working on a dev branch, they will have to rebase or else merge the changes of your branch into theirs.
  • If other collaborators and authors use commit signature verification as outlined above, their commits will likely say "Verified". After running this script, all commits by other authors will say "Unverified" -- since the script effectively "rewrites" commit history -- and they will need to run a script to batch re-sign all their previous commits, which will likely rewrite commit history again when changes are pushed.
# Bash/Zsh Function to Change Author for Commit(s)
#
# Adapted from:
#
# - https://stackoverflow.com/a/28845565/10237506
# - https://docs.github.com/en/enterprise/2.18/user/github/using-git/changing-author-info
#
# Directions:
#
# Enter either an *Email* to rewrite author of all commits by that email,
# or a space-separated list of *Git Commit SHA(s)* to change the author of.
#
# Examples:
# cca old@email.org
# cca sha-1 sha-2
#
# To find Commit SHAs for a Git User with email `current@email.org`, use:
#
# $ git --no-pager log --format=format:%H --author='current@email.org'
#
# Alternatively, navigate to below link (substituting org, repo,
# and Git author) and then *Click "Copy"*
#
# https://github.com/ORG/REPO/commits?author=AUTHOR
#
function cca() {
NUM_ARGS=$#
if [ $NUM_ARGS -eq 0 ]; then
echo "Usage: $0 <AUTHOR_EMAIL_OR_COMMIT_SHA_LIST...>"
echo
echo "Bash/Zsh Function to Change Author for Commit(s)"
echo
echo Function requires one or more arguments.
echo
echo Enter either an email to rewrite author of all commits by that email,
echo "or a space-separated list of Git Commit SHA(s) to change the author of."
echo
echo Examples:
echo " $ $0 old@email.org"
echo " $ $0 abc123 xyz321"
echo
echo To find all Commit SHAs for a Git User with email \`current@email.org\`, use:
echo " $ git --no-pager log --format=format:%H --author='current@email.org'"
return 1
fi
# Current Branch
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
GIT_NAME=$(git config --get user.name)
GIT_EMAIL=$(git config --get user.email)
echo "Branch: ${GIT_BRANCH}"
echo "Name: ${GIT_NAME}"
echo "Email: ${GIT_EMAIL}"
# Fix for error message from `git filter-branch`:
# Cannot create a new backup.
# A previous backup already exists in refs/original/
# Force overwriting the backup with -f
#
# Ref: https://stackoverflow.com/a/7654880/10237506
git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
# Check for ONE argument, and that the argument is an EMAIL
# Partial Credits: https://stackoverflow.com/a/4501833/10237506
if [[ $NUM_ARGS -eq 1 && "$1" =~ .+@.+ ]]; then
GIT_OLD_EMAIL="$1"
echo "Old Email: ${GIT_OLD_EMAIL}"
echo '[ Replacing Commit Author - Old Email > Email ]'
# To change the name and/or email address recorded in existing commits,
# you must rewrite the entire history of your Git repository.
#
# Credits: https://docs.github.com/en/enterprise/2.18/user/github/using-git/changing-author-info
FILTER_BRANCH_SQUELCH_WARNING=1 \
GIT_OLD_EMAIL="$GIT_OLD_EMAIL" \
GIT_NAME="$GIT_NAME" \
GIT_EMAIL="$GIT_EMAIL" \
git filter-branch --env-filter '
if [ "$GIT_COMMITTER_EMAIL" = "$GIT_OLD_EMAIL" ]
then
export GIT_COMMITTER_NAME="$GIT_NAME"
export GIT_COMMITTER_EMAIL="$GIT_EMAIL"
fi
if [ "$GIT_AUTHOR_EMAIL" = "$GIT_OLD_EMAIL" ]
then
export GIT_AUTHOR_NAME="$GIT_NAME"
export GIT_AUTHOR_EMAIL="$GIT_EMAIL"
fi
' \
--commit-filter '
if [[ "$GIT_COMMITTER_EMAIL" = "$GIT_NAME" || "$GIT_AUTHOR_EMAIL" = "$GIT_EMAIL" ]]
then
git commit-tree -S "$@";
fi
' \
--tag-name-filter cat \
-- --branches --tags
else
# Checkout the commit we are trying to modify
#
# The `-c advice.detachedHead=false` parameter will allow you to suppress the warning.
# Ref: https://stackoverflow.com/a/45652159/10237506
for GIT_COMMIT_SHA in "$@"; do
git -c advice.detachedHead=false checkout "${GIT_COMMIT_SHA}"
# Make the author change (use `--no-edit` to skip prompt to update commit message)
#
# Update to use `--reset-author` as mentioned in below, so we can also
# set Git committer instead of just the author. Also, use existing committed date.
#
# Refs:
# - https://stackoverflow.com/a/74856838/10237506
# - https://stackoverflow.com/a/61217637/10237506
git \
-c user.name="${GIT_NAME}" \
-c user.email="${GIT_EMAIL}" \
commit -S \
--amend \
--reset-author \
--no-edit \
--date="$(git --no-pager log -1 --format='%aD')"
GIT_NEW_COMMIT_SHA=$(git --no-pager log -1 --format="%H")
# Checkout the original branch
git checkout "${GIT_BRANCH}"
echo "Commit SHA: ${GIT_COMMIT_SHA}"
echo "Commit SHA (NEW): ${GIT_NEW_COMMIT_SHA}"
# Replace the old commit with the new one locally
git replace "${GIT_COMMIT_SHA}" "${GIT_NEW_COMMIT_SHA}"
done
# Rewrite all commits based on the replacement
#
# Also, sign the commits authored by this user if possible,
# and correct committer date for rewritten commit above.
#
# Refs:
# - https://stackoverflow.com/a/41883164/10237506
# - https://stackoverflow.com/a/24820045/10237506
FILTER_BRANCH_SQUELCH_WARNING=1 \
GIT_NAME=$GIT_NAME \
GIT_EMAIL=$GIT_EMAIL \
git filter-branch \
--env-filter \
'export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"' \
--commit-filter '
if [[ "$GIT_COMMITTER_EMAIL" = "$GIT_NAME" || "$GIT_AUTHOR_EMAIL" = "$GIT_EMAIL" ]]
then
git commit-tree -S "$@";
else
git commit-tree "$@";
fi
' \
-- --all
# Remove the replacement for cleanliness
for GIT_COMMIT_SHA in "$@"; do
git replace -d "${GIT_COMMIT_SHA}"
done
fi
# Seems to be needed for `--force-with-lease` to work!
git fetch
# Push the new history
if ! git push --force-with-lease; then
# Only use `--force` instead of `--force-with-lease`
# if the latter fails, and only after sanity checking
# with git log and/or git diff.
echo 'Git Push with `--force-with-lease` failed.'
# Ref: https://stackoverflow.com/a/14741036/10237506
if git diff --exit-code && [[ -z $(git status -uno --porcelain) ]]; then
echo Git Status/Diff - OK
N=5
echo [ Git Log - Showing Top ${N} Commits ]
git --no-pager log --graph --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(bold blue)<%an>%Creset' --abbrev-commit -n ${N}
echo
# Confirm w/ user to use `--force` for `git push`
printf '%s' 'Proceed with `git push --force`? [y/N] '
# Read Character, compatible with `zsh` and `bash`
# https://stackoverflow.com/a/30022297/10237506
read_char() {
stty -icanon -echo
eval "$1=\$(dd bs=1 count=1 2>/dev/null)"
stty icanon echo
}
read_char REPLY
echo "$REPLY" # (optional) move to a new line
if [[ $REPLY =~ ^[Yy]$ ]]; then
git push -f
echo 'Git Push with `--force` successful!'
fi
else
echo Git Status/Diff - ERROR, aborting.
return 1
fi
else
echo 'Git Push with `--force-with-lease` successful!'
fi
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment