Skip to content

Instantly share code, notes, and snippets.

@jerodg
Last active March 5, 2025 17:04
Show Gist options
  • Save jerodg/c1b9da7c3beb60c702f17dc34397bf01 to your computer and use it in GitHub Desktop.
Save jerodg/c1b9da7c3beb60c702f17dc34397bf01 to your computer and use it in GitHub Desktop.
Git Author Rewrite
#!/bin/bash
# ----------------------------------------------------------------------
# Author: JerodG <https://github.com/jerodg>
# File: reauthor.sh
#
# Purpose: Updates git commit history in multiple repositories by:
# - Changing author and committer details
# - Adding GPG signing information
# - Rebasing and re-signing all commits
#
# This script processes all git repositories in the current location
# and requires git-filter-repo to be installed.
# ----------------------------------------------------------------------
# Configuration
GIT_AUTHOR_NAME=""
GIT_AUTHOR_EMAIL=""
GIT_COMMITTER_NAME=""
GIT_COMMITTER_EMAIL=""
GIT_SIGNING_KEY=""
# Check that configuration variables have been set
if [ -z "$GIT_AUTHOR_NAME" ] || [ -z "$GIT_AUTHOR_EMAIL" ] || [ -z "$GIT_COMMITTER_NAME" ] || [ -z "$GIT_COMMITTER_EMAIL" ] || [ -z "$GIT_SIGNING_KEY" ]; then
echo "Please configure the GIT_AUTHOR_NAME, GIT_AUTHOR_EMAIL, GIT_COMMITTER_NAME, GIT_COMMITTER_EMAIL, and GIT_SIGNING_KEY variables before running this script."
exit 1
fi
# Color codes for terminal output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Log messages with appropriate colors
log_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
log_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
log_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Error handling function
handle_error() {
log_error "$1"
exit "${2:-1}" # Default to exit code 1 if not specified
}
# Verify git-filter-repo availability before proceeding
# Exit early if the required tool isn't available
if ! command -v git-filter-repo &> /dev/null
then
handle_error "git-filter-repo could not be found, please install it first." 2
fi
# Find all directories that are git repositories
folders=$(find . -maxdepth 1 -type d -name "zs.*" -exec test -e "{}/.git" ';' -print)
# Validate that at least one matching directory was found
# Exit early if no directories match the pattern
if [ -z "$folders" ]; then
handle_error "No git repositories found." 3
fi
# Count the number of folders for progress tracking
total_folders=$(echo "$folders" | wc -l)
current_folder=0
# Process each matching directory
for folder in $folders; do
current_folder=$((current_folder + 1))
log_info "Processing folder: $folder ($current_folder of $total_folders)"
# Navigate to target repository
cd "$folder" || handle_error "Failed to enter directory: $folder" 4
# Count total commits for progress reporting
total_commits=$(git rev-list --count --all 2>/dev/null)
if [ $? -ne 0 ]; then
log_warning "Could not count commits in $folder. Continuing anyway."
total_commits="unknown"
else
log_info "Found $total_commits commits to process"
fi
# Modify the commit history:
# - Updates author and committer information
# - Sets GPG signing key
# - Preserves original commit timestamps
log_info "Rewriting commit history with new identity..."
if ! git filter-repo --force \
--commit-callback '
commit.author_name = b"'"$GIT_AUTHOR_NAME"'"
commit.author_email = b"'"$GIT_AUTHOR_EMAIL"'"
commit.committer_name = b"'"$GIT_COMMITTER_NAME"'"
commit.committer_email = b"'"$GIT_COMMITTER_EMAIL"'"
commit.signing_key = b"'"$GIT_SIGNING_KEY"'"
commit.committer_date = commit.committer_date
commit.author_date = commit.author_date
' > /dev/null 2>&1; then
log_error "Failed to modify commit history in $folder. Continuing with next repository."
cd .. || handle_error "Failed to return to parent directory" 5
continue
fi
# Re-sign all commits with the configured GPG key
log_info "Re-signing all commits with GPG key..."
if ! git rebase --exec 'git commit --amend --no-edit -n -S' --root; then
log_error "Failed to re-sign commits in $folder. Manual intervention may be required."
# Creating a recovery script for this repository
echo "#!/bin/bash" > "../recover_$folder.sh"
echo "cd $folder" >> "../recover_$folder.sh"
echo "git rebase --abort" >> "../recover_$folder.sh"
chmod +x "../recover_$folder.sh"
log_warning "Created recovery script: recover_$folder.sh"
else
log_success "Successfully processed $folder"
fi
# Return to the parent directory before processing the next repository
cd .. || handle_error "Failed to return to parent directory" 5
done
# Final status report
if [ $current_folder -eq $total_folders ]; then
log_success "All $total_folders repositories have been processed."
else
log_warning "Processed $current_folder out of $total_folders repositories."
fi
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment