|
| 1 | +#!/bin/sh |
| 2 | + |
| 3 | +# Change the Git history of a repository |
| 4 | +# |
| 5 | +# Note: Running this script rewrites history for all repository collaborators. |
| 6 | +# After completing these steps, any person with forks or clones must fetch |
| 7 | +# the rewritten history and rebase any local changes into the rewritten history. |
| 8 | +# ================================================== # |
| 9 | + |
| 10 | +# Set global SHOULD_EXECUTE function to false so user must confirm |
| 11 | +SHOULD_EXECUTE=false |
| 12 | + |
| 13 | +# ----------- SET COLORS ----------- |
| 14 | +COLOR_RED=$'\e[31m' |
| 15 | +COLOR_CYAN=$'\e[36m' |
| 16 | +COLOR_YELLOW=$'\e[93m' |
| 17 | +COLOR_GREEN=$'\e[32m' |
| 18 | +COLOR_RESET=$'\e[0m' |
| 19 | + |
| 20 | +# Get PID of process to kill later if needed |
| 21 | +SCRIPT_PID=$$ |
| 22 | + |
| 23 | +echo "# " |
| 24 | +echo "# Change Author for Existing Commits" |
| 25 | +echo "# ----------------------------------" |
| 26 | + |
| 27 | +# ----------- Prompt user for email to replace ( OLD_EMAIL ) ----------- |
| 28 | +echo "# " |
| 29 | +echo "# 1. Enter the email address of the author you " |
| 30 | +echo "# would like to replace in the commit history." |
| 31 | +echo "# " |
| 32 | +read -e -p "# Email to Replace: ${COLOR_CYAN}" OLD_EMAIL |
| 33 | +echo -e "${COLOR_RESET}# " |
| 34 | +eval OLD_EMAIL="$OLD_EMAIL" |
| 35 | + |
| 36 | +# Check for blank email input |
| 37 | +if [ -z "$OLD_EMAIL" ]; then |
| 38 | + echo "# ${COLOR_RED}An email address is required. Please try again.${COLOR_RESET}" |
| 39 | + echo "# " |
| 40 | + kill "$SCRIPT_PID" |
| 41 | +else |
| 42 | + # Email input is not blank |
| 43 | + |
| 44 | + # Check if OLD_EMAIL exists in log |
| 45 | + OLD_EMAIL_EXISTS="$(git log --pretty=format:"%ae" | grep -w ${OLD_EMAIL})" |
| 46 | + |
| 47 | + # If OLD_EMAIL does NOT exist in log |
| 48 | + if [ -z "$OLD_EMAIL_EXISTS" ]; then |
| 49 | + # OLD_EMAIL does not exist in log |
| 50 | + echo "# ${COLOR_RED}The email '${OLD_EMAIL}' does not${COLOR_RESET}" |
| 51 | + echo "# ${COLOR_RED}exist in the log. Please check your spelling${COLOR_RESET}" |
| 52 | + echo "# ${COLOR_RED}and try again.${COLOR_RESET}" |
| 53 | + echo "# " |
| 54 | + kill "$SCRIPT_PID" |
| 55 | + fi |
| 56 | +fi |
| 57 | + |
| 58 | +# ----------- Prompt user for correct email ( CORRECT_EMAIL ) ----------- |
| 59 | +echo "# 2. Enter a new/corrected email for this user." |
| 60 | +echo "# " |
| 61 | +read -e -p "# New Email: ${COLOR_CYAN}" CORRECT_EMAIL |
| 62 | +echo -e "${COLOR_RESET}# " |
| 63 | +eval CORRECT_EMAIL="$CORRECT_EMAIL" |
| 64 | + |
| 65 | +# Check for blank email input |
| 66 | +if [ -z "$CORRECT_EMAIL" ]; then |
| 67 | + echo "# ${COLOR_RED}An email address is required. Please try again.${COLOR_RESET}" |
| 68 | + echo "# " |
| 69 | + kill "$SCRIPT_PID" |
| 70 | +fi |
| 71 | + |
| 72 | +# ----------- Prompt user for correct name ( CORRECT_NAME ) ----------- |
| 73 | +echo "# 3. Enter the new/corrected name for this user." |
| 74 | +echo "# ${COLOR_YELLOW}(Be sure to enclose name in quotes)${COLOR_RESET}" |
| 75 | +echo "# " |
| 76 | +read -e -p "# New Name: ${COLOR_CYAN}" CORRECT_NAME |
| 77 | +echo -e "${COLOR_RESET}# " |
| 78 | +eval CORRECT_NAME="$CORRECT_NAME" |
| 79 | + |
| 80 | +# Check for blank name input |
| 81 | +if [ -z "$CORRECT_NAME" ]; then |
| 82 | + echo "# ${COLOR_RED}A name is required. Please try again.${COLOR_RESET}" |
| 83 | + echo "# " |
| 84 | + kill "$SCRIPT_PID" |
| 85 | +fi |
| 86 | + |
| 87 | +# ----------- Prompt user for remote (Default: 'origin' ) ----------- |
| 88 | +echo "# 4. Enter the remote you would like to alter." |
| 89 | +echo "# ${COLOR_YELLOW}(Default: origin)${COLOR_RESET}" |
| 90 | +echo "# " |
| 91 | +read -e -p "# Remote Name: ${COLOR_CYAN}" -i "origin" REPO_REMOTE |
| 92 | +echo -e "${COLOR_RESET}# " |
| 93 | +eval REPO_REMOTE="${REPO_REMOTE:-origin}" |
| 94 | + |
| 95 | +# Check for blank remote input |
| 96 | +if [ -z "$REPO_REMOTE" ]; then |
| 97 | + echo "# ${COLOR_RED}A remote is required. Please try again.${COLOR_RESET}" |
| 98 | + echo "# " |
| 99 | + kill "$SCRIPT_PID" |
| 100 | +fi |
| 101 | + |
| 102 | +# Check if remote exists in repository |
| 103 | +VALID_REMOTES="$(git remote | grep -w ${REPO_REMOTE})" |
| 104 | +if [ -z "$VALID_REMOTES" ]; then |
| 105 | + # Remote does not exist |
| 106 | + echo "# ${COLOR_RED}The remote '${REPO_REMOTE}' does not exist for${COLOR_RESET}" |
| 107 | + echo "# ${COLOR_RED}this repository. Please check your${COLOR_RESET}" |
| 108 | + echo "# ${COLOR_RED}spelling and try again.${COLOR_RESET}" |
| 109 | + echo "# " |
| 110 | + kill "$SCRIPT_PID" |
| 111 | +fi |
| 112 | + |
| 113 | +# ----------- Have the user confirm before executing ----------- |
| 114 | +# read -e -p "# ${COLOR_RED}Are you sure you wish to execute this command?${COLOR_RESET} [y/n]" USER_CONFIRM |
| 115 | + |
| 116 | +while true; do |
| 117 | + echo "# 5. ${COLOR_RED}Are you sure you want to rewrite the entire ${COLOR_RESET}" |
| 118 | + echo "# ${COLOR_RED}history of your Git repository?${COLOR_RESET}" |
| 119 | + echo "# " |
| 120 | + echo "# ${COLOR_YELLOW}Note: Running this script rewrites history for all${COLOR_RESET}" |
| 121 | + echo "# ${COLOR_YELLOW}repository collaborators. Any person with forks ${COLOR_RESET}" |
| 122 | + echo "# ${COLOR_YELLOW}or clones must fetch the rewritten history and${COLOR_RESET}" |
| 123 | + echo "# ${COLOR_YELLOW}rebase any local changes into the rewritten history.${COLOR_RESET}" |
| 124 | + echo "# " |
| 125 | + read -e -p "# [y/n]: ${COLOR_CYAN}" USER_CONFIRM |
| 126 | + echo -e "${COLOR_RESET}# " |
| 127 | + case $USER_CONFIRM in |
| 128 | + [Yy]* ) SHOULD_EXECUTE=true; break;; |
| 129 | + [Nn]* ) SHOULD_EXECUTE=false; break;; |
| 130 | + * ) echo "# "; echo "# ${COLOR_YELLOW}You must enter 'Y' to confirm, or 'N' to cancel${COLOR_RESET}"; echo "# ";; |
| 131 | + esac |
| 132 | +done |
| 133 | + |
| 134 | +# ----------- If SHOULD_EXECUTE is true, rewrite repo history, otherwise, kill ----------- |
| 135 | +if [ "$SHOULD_EXECUTE" = true ] ; then |
| 136 | + # ----------- Alter commits and rewrite history ----------- |
| 137 | + git filter-branch --env-filter ' |
| 138 | + if [ "$GIT_COMMITTER_EMAIL" = "'"$OLD_EMAIL"'" ] |
| 139 | + then |
| 140 | + export GIT_COMMITTER_NAME="'"$CORRECT_NAME"'" |
| 141 | + export GIT_COMMITTER_EMAIL="'"$CORRECT_EMAIL"'" |
| 142 | + fi |
| 143 | + if [ "$GIT_AUTHOR_EMAIL" = "'"$OLD_EMAIL"'" ] |
| 144 | + then |
| 145 | + export GIT_AUTHOR_NAME="'"$CORRECT_NAME"'" |
| 146 | + export GIT_AUTHOR_EMAIL="'"$CORRECT_EMAIL"'" |
| 147 | + fi |
| 148 | + ' --tag-name-filter cat -- --branches --tags |
| 149 | + |
| 150 | + # ----------- Show Success Message ----------- |
| 151 | + echo "# " |
| 152 | + echo "# ${COLOR_GREEN}Successfully Updated Local Author Info${COLOR_RESET}" |
| 153 | + echo "# " |
| 154 | + echo "# Preparing to push to remote '${REPO_REMOTE}'..." |
| 155 | + echo "# ${COLOR_YELLOW}(Now is your chance to cancel)${COLOR_RESET}" |
| 156 | + echo "# " |
| 157 | + # Sleep for a sec to let user cancel |
| 158 | + sleep 5 |
| 159 | + echo "# " |
| 160 | + |
| 161 | + # ----------- Update Remote ----------- |
| 162 | + git push --force --tags "$REPO_REMOTE" 'refs/heads/*' |
| 163 | + |
| 164 | + echo "# " |
| 165 | + echo "# ${COLOR_GREEN}Successfully Updated Remote Author Info${COLOR_RESET}" |
| 166 | + echo "# " |
| 167 | + echo "# ${COLOR_GREEN}The author info for commits linked to${COLOR_RESET}" |
| 168 | + echo "# ${COLOR_GREEN}'${OLD_EMAIL}' have been updated to${COLOR_RESET}" |
| 169 | + echo "# ${COLOR_GREEN}'${CORRECT_NAME} <${CORRECT_EMAIL}>' and the changes${COLOR_RESET}" |
| 170 | + echo "# ${COLOR_GREEN}have been pushed to remote '${REPO_REMOTE}'. ${COLOR_RESET}" |
| 171 | +else |
| 172 | + # ----------- User Canceled ----------- |
| 173 | + echo "# " |
| 174 | + echo "# ${COLOR_GREEN}Successfully Canceled.${COLOR_RESET}" |
| 175 | + echo "# ${COLOR_GREEN}No changes were made.${COLOR_RESET}" |
| 176 | +fi |
0 commit comments