Skip to content

Instantly share code, notes, and snippets.

@wireboy5
Last active November 19, 2024 20:27
Show Gist options
  • Save wireboy5/f342002b8e1c72cb41471928ee4ff807 to your computer and use it in GitHub Desktop.
Save wireboy5/f342002b8e1c72cb41471928ee4ff807 to your computer and use it in GitHub Desktop.
A basic utility to purge a the given emails from a github repo's commit author entries. WARNING: It is not the greatest idea to use this in a repository with multiple contributors, as it will re-sign EVERY commit with your signature. Requires git-filter-repo.
#!/bin/python3
# Simple utility to clone a given list of github repos via ssh
# and map every commit with the given email to another.
import os
import subprocess
import shutil
import json
import time
repos = [
# Repos here in the form "Organization/repo"
]
names = {
# emails here, in the form old_email:new_email
}
os.makedirs("./cleaner/repos", exist_ok=True)
os.makedirs("./cleaner/backups", exist_ok=True)
# Clone each repo
for repo in repos:
os.makedirs(f"./cleaner/repos/{repo}", exist_ok=False)
print(f"Fetching [email protected]:{repo}.git")
subprocess.run(["git", "clone", f"[email protected]:{repo}.git", f"./cleaner/repos/{repo}"])
# Lets not get rate limited
time.sleep(0.3)
# Make sure each repo is up to date
for repo in repos:
print(f"Pulling down branches for {repo}")
subprocess.run(["git", "pull", "--all"], cwd=f"./cleaner/repos/{repo}")
time.sleep(0.3)
# Backup each repo
for repo in repos:
print(f"Creating backup of {repo} in ./cleaner/backups/{repo}.zip")
#os.makedirs(f"./cleaner/backups/{'/'.join(repo.split('/')[:-1])}", exist_ok=True)
shutil.make_archive(f"./cleaner/backups/{repo}.zip", "xztar", f"./cleaner/repos/{repo}")
# For each repo, clean out each name
for repo in repos:
cwd = f"./cleaner/repos/{repo}"
print(f"Cleaning {repo}")
cleaner = f"return email if email.decode() not in \"{','.join(names.keys())}\" else {json.dumps(names)}[email.decode()].encode()"
print("using", cleaner)
subprocess.run(["git", "filter-repo", "--force", "--partial", "--email-callback", cleaner], cwd=cwd)
# Run git GC
for repo in repos:
print(f"Running GC on {repo}")
subprocess.run(["git", "gc", "--prune=now", "--aggressive"], cwd=f"./cleaner/repos/{repo}")
# Resign all commits and force push
for repo in repos:
cwd = f"./cleaner/repos/{repo}"
print(f"Resigning all commits for {repo}")
subprocess.run(["git", "rebase", "--exec", 'git commit --amend --no-edit -n -S', '--root'], cwd=cwd)
subprocess.run(["git", "rebase", "--continue"], cwd=cwd)
subprocess.run(["git", "rebase", "--committer-date-is-author-date", "--root"],cwd=cwd)
subprocess.run(["git", "rebase", "--continue"], cwd=cwd)
print(f"Force pushing {repo}")
subprocess.run(["git", "push", "--force"], cwd=cwd)
print("Finished cleaning repositories. Backups located in ./cleaner/backups/")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment