同一PC内で複数Githubアカウントを自動で切り替える
モチベ
会社でGitHub Enterpriseを導入している関係で、
個人用のGitHubのアカウントとの切り替えをしたい。
が、しかし
git config --global user.name = "hoge"
git config --global user.email = "fuga@example.com"
だの
git clone git@github.com:ore/repo
cd repo
git config --local user.name = "hoge"
git config --local user.email = "fuga@example.com"
だの毎回打ちたくないので自動で切り替えて欲しい。
そこで、今回はGit Hookを利用してなんとかしたものを備忘録がてら共有します。
筆者環境
- MacBook Pro (16-inch, 2019)
- macOS 10.15.7
- git version 2.31.1
概要
cloneしてくる時にアカウントを判別して、local configの user.name, user.email
を自動でセットする。
準備
今回はGitHubとGitHub Enterpriseを切り替える程でやりますが、GitHubのアカウント2つの切り替えも同じような手順でできると思います。
SSHの設定
とりあえず ssh-keygen
でkeyを生成。
別にここでアカウントごとにkeyを分ける必要はないですが、今回は分けるということで。
ssh-keygen -f ~/.ssh/ghe_rsa
ssh-keygen -f ~/.ssh/github_rsa
ls ~/.ssh # ghe_rsa ghe_rsa.pub github_rsa github_rsa.pub
生成した公開鍵 *.pub は各アカウントで登録しておきます。
次に ~/.ssh/config に以下を追記します。
Host primary.ghe
HostName ghe.hoge.com
IdentityFile ~/.ssh/ghe_rsa
User git
Host secondary.github
HostName github.com
IdentityFile ~/.ssh/github_rsa
User git
ここで注意、SSH的にはHost名にアンダースコアが使えますが、後でgit側でエラーが出るので区切る場合はピリオドを使うことをお勧めします。
gitconfig
次に ~/.gitconfig に以下を追記します。
[user "ssh://git@primary.ghe/"]
name = hoge
email = hoge@example.com
[user "ssh://git@secondary.github/"]
name = fuga
email = fuga@example2.com
今回はアカウントを使い分けることが目的なので、globalのuserはhost別に定義しておきます。
Git hook
Git hookについてはこちらを参照してください。
まずはhookを置く場所を決めます。
~/.gitconfig にパスを書けばいいのでどこでも大丈夫です。
cd {hookを置きたい場所}
mkdir -p .git_template/hooks
hookを置く場所を決めてディレクトリを作ったら、 ~/.gitconfig にパスを追記します。
git config --global init.templatedir '{hookを置いたディレクトリ}/.git_template'
今回は git clone
を叩いた時にhookが発火して欲しいのですが、cloneした時だけ発火するhookは存在しません。そこでcloneした後はcheckoutする仕様を利用して、checkoutした後に発火するhook内でcloneかどうかを判別することにします。
hookは実行権限が必要なので、先に与えてしまいましょう。
cd .git_template/hooks
touch post-checkout
chmod +x post-checkout
post-checkoutの中身は以下のようにします。
#!/bin/sh
readonly PREVIOUS_HEAD=$1
readonly NOW_HEAD=$2
readonly BRANCH_SWITCH=$3 # ブランチをチェックアウトした場合は1, ファイルをチェックアウトした場合は0
readonly Z40="0000000000000000000000000000000000000000"
readonly origin_name="$(git remote | head -1)"
current_remote_url="$(git config --get --local remote.$origin_name.url)"
local_name="$(git config --local --get user.name)"
local_email="$(git config --local --get user.email)"
# git cloneした場合のPREVIOUS_HEADは0が40個で、BRANCH_SWITCHは1
if [ "$PREVIOUS_HEAD" = "$Z40" -o "$BRANCH_SWITCH" = "1" ]; then
if [ -z "$current_remote_url" ]; then
echo "\033[31mfatal: No remote URL\033[m"
fi
case $current_remote_url in
*://*)
# Normalize URL: remove leading "git+"
# e.g.) "git+ssh://user@host/path/" ==> "ssh://user@host/path/"
current_remote_url=$(echo $current_remote_url | sed 's/^git\+//')
;;
*:*)
# Convert scp-style URL to normal-form
# e.g.) "user@host:path/" ==> "ssh://user@host/path/"
current_remote_url=$(echo $current_remote_url | sed 's/\(.*\):ssh:\/\/\1\//')
;;
esac
if [ -z "$local_name" ]; then
local_name="$(git config --get-urlmatch user.name $current_remote_url)"
git config --local user.name "$local_name"
echo "config: set local user.name $local_name"
fi
if [ -z "$local_email" ]; then
local_name="$(git config --get-urlmatch user.email $current_remote_url)"
git config --local user.email "$local_email"
echo "config: set local user.email $local_email"
fi
else
# 通常のgit checkout後のhookを書く場合にはここに書く
fi
これで終わりでもいいんですが、 git init
した時などは自動的にlocalの user.name, user.email
が設定されないので、globalの値が使われることになります。
そこで、pre-commit hookを作ることでlocalに user.name, user.email
が設定されていない場合commitできないようにします。
この場合pre-commitにも実行権限をつけることを忘れないようにしましょう。
#!/bin/sh
if [ -z "$(git config --local --get user.name)" ]; then
echo "\033[31mfatal: user.name is not set locally\033[m"
exit 1
fi
if [ -z "$(git config --local --get user.email)" ]; then
echo "\033[31mfatal: user.email is not set locally\033[m"
exit 1
fi
使い方
git clone
するときはあらかじめ ~/.ssh/config に記述してあるhostを指定してcloneする。
git clone git@primary.ghe:hoge/repo
注意点
- git initした場合はcheckoutが行われないのでpost-checkout hookが発火しません
- 新しく空のレポジトリを作って、それをそのままcloneした場合も同様にcheckoutが行われないのでpost-checkout hookが発火しません
追記
@ulwluさんからご指摘いただきましたが、アカウントごとにディレクトリを分けるのであれば includeif
を用いた方がよりシンプルで良いと思います。
参考
Discussion