|
# 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 [email protected] |
|
# cca sha-1 sha-2 |
|
# |
|
# To find Commit SHAs for a Git User with email `[email protected]`, use: |
|
# |
|
# $ git --no-pager log --format=format:%H --author='[email protected]' |
|
# |
|
# 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 [email protected]" |
|
echo " $ $0 abc123 xyz321" |
|
echo |
|
echo To find all Commit SHAs for a Git User with email \`[email protected]\`, use: |
|
echo " $ git --no-pager log --format=format:%H --author='[email protected]'" |
|
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 |
|
} |