Gobble up pudding

プログラミングの記事がメインのブログです。

MENU

GitHubのコミット履歴から個人のメールアドレスを消す

スポンサードリンク

背景

GitHubのコミット履歴にはメールアドレスが埋め込まれており、誰でも確認できる。スパム対策として、過去のコミット履歴を書き換えつつ、今後のコミットにはno-replyアドレスを使うようにした。
ちなみに2014年くらい前からGitHubでno-replyのアドレスが設定できるようになった。

やりたいこと

  • emailアドレスをGitHubのno-replyアドレスに変更
  • GitHubの名前を途中で変えてるのでそれも変えたい(今回はそのスクリプトはないけど、--name-callbackで条件書けばできる)。

方針

  • 今後のコミット → GitHubのno-replyアドレスに変更
  • 過去のコミット → git-filter-repo で一括書き換え
  • プロフィールのメールアドレス → Settings → Emails → "Keep my email addresses private" をオン

no-replyアドレスの確認

GitHub Settings → Emails に表示されている以下の形式のアドレスを使う。

12345678+username@users.noreply.github.com

今後のコミットに使うよう設定:

git config --global user.email "12345678+username@users.noreply.github.com"

systemレベルも変えておく(管理者権限が必要): 基本systemレベルでは設定しないため消しておく

git config --system --unset user.email

確認

git config --global user.email  # 新しいものに変わっていればOK
git config --system user.email  # 何も出ない

プロジェクト単位でe-mail設定しているものがあるか探すシェル

通常ないと思うがメールアドレスを使い分けている場合にあり得る

Mac/Linux/WSL:

find ~ -path "*/.git/config" -exec grep -H "email" {} \;

Windows:

Get-ChildItem -Path $HOME -Recurse -Force -Filter "config" -ErrorAction SilentlyContinue |
  Where-Object { $_.DirectoryName -like "*\.git" } |
  Select-String "email" |
  Select-Object Path, Line

※Git Bashでやってもいいが、遅いのでPowerShellを推奨。PowerShellは便利なんだけど、記述が長すぎて嫌いだけれども(エイリアスを使うのは非推奨みたいだし)。


git-filter-repo のインストール

※Pythonが入っている必要があります。

pip install git-filter-repo

GitHub CLI のインストール

リポジトリ一覧の取得に使う。

Mac:

brew install gh

Windows:

winget install GitHub.cli

Linux/WSL:

curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null
sudo apt update && sudo apt install gh

インストール後に認証:

gh auth login

一括書き換えスクリプト

公開リポジトリだけに制限しています。
注意事項:テストしてから実行してください。責任は負えません。 自分の場合、完全に自分のリポジトリだけだと思ったら、
忘れているのがあって、callbackで条件文を入れず、フォークリポジトリで他の人のユーザ名をかきかえてしまい…
reflogから戻しました。

以下のは書き換えた後force pushまで行います。

gh repo list $GH_USER --visibility=public --json name --jq '.[].name'

以下

OLD_EMAIL="12345678@example.com"
OLD_USER="username"
NEW_EMAIL="12345678+username@users.noreply.github.com"
GH_USER="username"

を書きかえてください。 なおHTTPSでリモートのURLを書き換えているのでSSHの場合は適宜書き換えてください。 名前を変えてない限りはOLD_USERはGH_USERと一緒でいいです。

Bash版(Mac/WSL/Linux)

#!/bin/bash
OLD_EMAIL="12345678@example.com"
OLD_USER="username"
NEW_EMAIL="12345678+username@users.noreply.github.com"
GH_USER="username"

REPOS=($(gh repo list $GH_USER --visibility=public --json name --jq '.[].name'))

for REPO in "${REPOS[@]}"; do
    echo "Processing $REPO..."
    git clone "https://github.com/$GH_USER/$REPO.git" "tmp-$REPO"
    cd "tmp-$REPO"
    git filter-repo --name-callback "return b'$GH_USER' if name == b'$OLD_NAME' else name" --email-callback "return b'$NEW_EMAIL' if email == b'$OLD_EMAIL' else email"
    if [ $? -ne 0 ]; then
        echo "SKIP: $REPO のfilter-repoに失敗しました"
        cd ..
        rm -rf "tmp-$REPO"
        continue
    fi
    git remote add origin "https://github.com/$GH_USER/$REPO.git"
    git push --force --all
    if [ $? -ne 0 ]; then
        echo "SKIP: $REPO のpushに失敗しました(protected branch?)"
        cd ..
        rm -rf "tmp-$REPO"
        continue
    fi
    git push --force --tags
    cd ..
    rm -rf "tmp-$REPO"
done

PowerShell版(Windows)

PowerShellのスクリプト実行ポリシーをセッション限りで緩和しておく(管理者権限不要):

Set-ExecutionPolicy -Scope Process RemoteSigned

スクリプト本体:

$OLD_EMAIL="12345678@example.com"
$OLD_USER="username"
$NEW_EMAIL = "12345678+username@users.noreply.github.com"
$GH_USER = "username"

$REPOS = gh repo list $GH_USER --visibility=public --json name --jq '.[].name'

foreach ($REPO in $REPOS) {
    Write-Host "Processing $REPO..."
    git clone "https://github.com/$GH_USER/$REPO.git" "tmp-$REPO"
    Set-Location "tmp-$REPO"
    git filter-repo --name-callback "return b'$GH_USER' if name == b'$OLD_NAME' else name" --email-callback "return b'$NEW_EMAIL' if email == b'$OLD_EMAIL' else email"
    if ($LASTEXITCODE -ne 0) {
        Write-Host "SKIP: $REPO のfilter-repoに失敗しました"
        Set-Location ..
        Remove-Item -Recurse -Force "tmp-$REPO"
        continue
    }
    git remote add origin "https://github.com/$GH_USER/$REPO.git"
    git push --force --all
    if ($LASTEXITCODE -ne 0) {
        Write-Host "SKIP: $REPO のpushに失敗しました(protected branch?)"
        Set-Location ..
        Remove-Item -Recurse -Force "tmp-$REPO"
        continue
    }
    git push --force --tags
    Set-Location ..
    Remove-Item -Recurse -Force "tmp-$REPO"
}

注意点

  • git filter-repo はclone時のoriginを意図的に削除する(誤push防止のため)。そのため git remote add origin が必要
  • コミットのhashは変わるが、AuthorDate/CommitDateは変わらない
  • forkされたリポジトリには変更が反映されない(fork側の歴史はそのまま残る)
  • ローカルの既存cloneはforce push後に git pull できなくなるため、再cloneが必要
  • protected branchがある場合はGitHub側でブランチ保護を一時的に外してからpushする
  • 手元で自分のケースではうまくいっているのを確認しているが、お試しのプロジェクトで一度うまくいくか確認して実行したほうが良いです。
  • 自分の手元のリポジトリはgit cloneし直すのが良いかと思います。