複数のGitアカウントを手動切替不要で運用する

※ この記事は、個人で2つ以上の無料GitHubアカウントを保持することを意図したものではありませんし、私自身無料アカウントを複数持つことはしておりません。無料アカウントを複数持つことは Github の規約違反になりますのでご注意ください（詳細は [こちらのコメント](https://qiita.com/KeyMama/items/f9291bb125ee94b52b78#comment-8acad64d0658e9940c76) にて）。

## 結論
- git configの`url`の設定を利用して、リポの中では自動的に目的のsshホストが使われるようにする。
- `git clone`が叩かれたことを`trap DEBUG`で検知してコマンド実行前にフラグを立て、Githooksの`post-checkout`内で、clone先ディレクトリの位置に応じて利用するgitアカウント情報をgitconfigに書き込む。

## 問題
一つのマシン上で複数のGithubアカウントを使い分けている人がどのくらいいるのかわかりませんが、私は仕事とプライベートのGithubアカウントを分けています。
案件によっては求めに応じてその会社用のアカウントを作ったりします。

ただ、複数持っていても運用がつらくなるのは避けたいです。

- アカウントの切り替えやリポの設定を楽にしたい。全部自動にしたい。
- SSHの設定を覚えてなくてもいいようにしたい。
- 間違えて仕事のリポにプライベートのアカウントでコミットしてしたり、プライベートのリポに仕事のアカウントでコミットしたりするのを防ぎたい。（[gitフックを使って職場のレポジトリに趣味のアカウントでcommitするのを防ぐ](https://qiita.com/akameco/items/7ce512fe86452b7c5208#_reference-c58aace492614c678f6a)という記事もあるんですが、コミットのときに実行されるのではなく、クローンのときに自動で設定したい）

ということで、

1. 複数アカウントの設定のしかた
2. 複数アカウントをどうやって楽に運用するか

を書きます。これによって、最初から最後まで（`git clone`前から`push`後まで）アカウントの切り替えを手動でやる必要も、アカウントを意識する必要も無くなりました。何らかのGitクライアントとか ghq みたいなツールでもちょっといじればこういうことができるものはあると思いますが、シェルスクリプトでもある程度できそうだよという記事です。

## 環境
macOS

```bash
sw_vers
ProductName:	Mac OS X
ProductVersion:	10.14.5
BuildVersion:	18F132
```

OpenSSH

```bash
ssh -V
OpenSSH_7.9p1, LibreSSL 2.7.3
```

Git

```bash
git --version
git version 2.23.0
```

仕事用のコードは`work`ディレクトリ配下、個人用のコードは`private`ディレクトリ配下で管理しているとします。

## 設定のしかた
知っている方は飛ばしてください。
### SSHキーを複数用意し、それぞれのGithubアカウントで公開鍵を設定
GithubではSSHキーを使い回せないのでアカウントの数分作ります。Githubじゃなくてもキーの使い回しは避けたほうがいいです。キーが流出しても被害が広がりにくいので。キーの作成方法がわからなければ、タイトルで煽ってるだけの[お前らのSSH Keysの作り方は間違っている](https://qiita.com/suthio/items/2760e4cff0e185fe2db9)等を参考に。

### ~/.ssh/configの設定

```
Host github_p
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github_private
IdentitiesOnly yes
UseKeychain yes
AddKeysToAgent yes

Host github_w
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_github_work
IdentitiesOnly yes
UseKeychain yes
AddKeysToAgent yes
```

- `Host`
値が一意である必要があるので、それぞれ別の名前にします。
- `HostName`, `User`
ここはいつも通り`github.com`と`git`です。
- `IdentityFile`
作成した秘密鍵のファイルパスです。適切に変更します。
- `IdentitiesOnly`
ssh-agentが複数のアイデンティティを保持しているときでも、ssh_configファイル内で明示的に設定されたアイデンティティのみを使用するかどうかを指定します。**まさに今回のようなユースケースのためにある設定項目で、これを忘れると、github_pで接続したいのに、勝手に忖度してgithub_wで接続してしまう、みたいなことが起きます。**
- `UseKeychain`
macOSのみ設定可能。パスワードやアカウント情報を管理するアプリであるKeychainにパスフレーズを保存するかどうか、そこから探すかどうかを指定します。macOSでない場合や、macOSでも古いバージョンのOpenSSHでは動かないらしいので、そんなオプションないって言われたら`UseKeychain`の行を消してください。
- `IgnoreUnknown`
ちなみに、そんなオプションないと言われるのを避けるためにこの設定の最初のほうに入れて、認識されないオプションを無視するという手もあるのですが、万が一、有効な設定なのにタイポしたために無視されるという危険もあるので、この設定は利用していません。
- `AddKeysToAgent`
キーとパスフレーズをssh-agentへ自動的に追加するかどうかを指定します。何度もssh-addするのは面倒くさいのでyesにします。この設定を使うのはやめようみたいな記事を見かけましたが、私はトラブったことはないです。その記事ではホントにこの設定が原因なのか検証されてなかったので、あまり気にしていません。

詳しくは`man ssh_config`を見てください。

## どうやって運用するか
### グローバルのユーザ情報を設定しない

```bash
git config --global --unset user.name
git config --global --unset user.email
```

```:~/.gitconfig
[user]
# nameが設定されていないことを確認
# emailが設定されていないことを確認
[url "github_p"]
# 件のSSHキーに関するURL短縮の設定がされていないことを確認
[url "github_w"]
# 件のSSHキーに関するURL短縮の設定がされていないことを確認
```

誤ったアカウントで操作したくないので、どのユーザ情報もデフォルトにはしないよう、globalにはユーザ情報は設定しません。`git config --local`のほうで適切に設定されない限り、`git@github.com`でアクセスできないようになります。安心。

### ローカルで user.name, user.email, url.<base>.insteadOfの設定をする
```bash
cd 個人の/既存の/リポジトリの/パス
git config --local user.name "個人ユーザ名"
git config --local user.email "個人メールアドレス"
# url."ssh_configで設定したHostの値".insteadOf という形式にします
git config --local url."github_p".insteadOf "git@github.com"


cd 仕事の/既存の/リポジトリの/パス
git config --local user.name "仕事ユーザ名"
git config --local user.email "仕事メールアドレス"
# url."ssh_configで設定したHostの値".insteadOf という形式にします
git config --local url."github_w".insteadOf "git@github.com"
```

```:.git/config
[user]
  user = "個人ユーザ名"
  email = "個人メールアドレス"
[url "github_p"]
  insteadOf = "git@github.com"
```

すでにクローンされてローカルマシンにあるリポジトリについては、リポごとにユーザ情報を設定します。設定し直す必要があるリポジトリが多かったらスクリプト書いたほうがいいです。

ポイントは`url`の設定です。

- `url.<base>.insteadOf`
gitコマンドを実行する際、`insteadOf`で指定した文字列で始まるURLについて、`insteadOf`で指定した文字列の部分が`<base>`で指定した文字列に書き換えられた状態で実行されます。
例えば、上記の`github_p`の設定をしてそのディレクトリ内でリモートとやり取りするgitコマンドを実行すると、
`git@github.com:個人ユーザ/あるリポジトリ.git` -> `github_p:個人ユーザ/あるリポジトリ.git`
というふうに書き換えられて実行されます。`git push`とか、明示的にURLを指定しないときもこの設定は有効なので便利です。`git@github.com`以外にも、任意の文字列を複数登録しておけます。
- `sshCommand`という設定項目もあるのですが、SSHの設定は~/.sshにまとめたく、散り散りに管理したくないので使いません。

### noreplyメールアドレスの利用（オプショナル）
ちなみに、これはオプショナルな設定ですが、色々な理由で個人のメールアドレスをさらしたくないケースがあると思います。その場合、登録したメールアドレスではなく、Githubから提供されるno-replyメールアドレスを利用することもできます。
`Setting` -> `Emails` -> `Keep my email addresses private`をチェックするだけです。`Primary email address`の説明のところにno-replyメールアドレスが表示されて、利用できるようになります。
参考: https://help.github.com/en/articles/setting-your-commit-email-address


### git cloneのSSH設定を自動で切り替える
もうひと押し。
上記のリポごとの`.git/config`の設定は一度設定してしまえば良いのですが、最初の`git clone`のときにはそもそもローカルにリポがないので設定できません。毎回クローン後にいちいち設定するのも面倒ですし、間違いの元です。

#### 方法1: 単純に`git clone`をラップするコマンドを作って、`git colne`を使わずにそれを使う
一番簡単な方法ですが、間違って`git clone`を使ってしまうのが嫌ですし、仮に別のプラグインを入れてクローンしたりするとダメです。

#### 方法2: cdとgitコマンドのラッパー関数を作ってエイリアスとして設定する
そこで以下のGistを作りました。Gist冒頭部のコメントの通りに設定して、rcファイル等に貼り付けてロードし直すと動きます。cdとgitコマンドをエイリアスで上書きしています。直接`cd`や`git`という名前で関数を定義しないのは、上書き前の挙動をさせたいときに`\cd`や`\git`だけでできるからです。
https://gist.github.com/KeyAmam/2cde31a9c2ca4b1af44251c3ac628dec

このGistが何をしてくれるかというと、
- `git clone`実行時のSSH設定の自動切り替え。`work`ディレクトリ配下で実行した場合（直下ではなく、配下ならどこでもいい）は`github_w`、`private`ディレクトリ配下で実行した場合は`github_p`、それ以外のディレクトリでは切り替えなしでcloneするようになります。
- `git clone`後の、上記の`user.name`・`user.email`・`url.<base>.insteadOf`の自動設定
- `--force-url`オプションで`git clone`時の自動切り替えOFF。
- GPGキーのキー生成とgitconfigへの自動設定（`git clone`実行前に、上記Gistの65行目をアンコメントし、別のGist https://gist.github.com/KeyAmam/74d4aefcd161410bf1487714b829a6ce を`~/.scripts/git-set-gpg-key.sh`に保存しておいた場合）

コードが散らばらないので良いのですが、こちらも別のプラグイン等を入れてcd以外でもディレクトリ移動したりすると具合が悪いので結局方法3にしました。

#### 方法3: GitHooksで`git clone`のみをフックする
記事を書きました: [GitHooksでgit cloneだけをフックする](https://qiita.com/KeyAmam/items/4c0b85be475f1f01a934)

### GPGキーの設定（オプショナル）
これはオプショナルな設定ですが、自分のコミットであることの信頼性を高めるためにGPGキーを設定します。基本的に、リポごとではなく、アカウントごとに1つのGPGキーを設定してます。
`git clone`実行前に、上記Gistの65行目をアンコメントし、別のGist https://gist.github.com/KeyAmam/74d4aefcd161410bf1487714b829a6ce を`~/.scripts/git-set-gpg-key.sh`に保存しておくと、GPGキーの設定が走るようになります。
設定したいリポに行って、`git-set-gpg-key.sh`を単体で実行することもできます。

GPGキー生成時に名前とメールアドレスを聞かれますが、Githubのアカウント名とメールアドレスを入れておきます。本名を聞かれますが本名である必要はありません。

ローカルの設定後、`gpg --armor --export メールアドレスやGPGキーIDなどキーを特定できる文字列 | pbcopy`でクリップボードにGPG公開鍵をコピーし、Githubの`Settings` -> `SSH and GPG keys` -> `New GPG Keys`で貼り付け保存で完了です。これでコミットするとGithub上で`verified`アイコンが表示されるのが確認できます。

## まとめ
まとめると、以下2つの対策によって、複数のGithubアカウントを持っていても、ほとんどアカウントについて意識する必要がなくなりました。気にするのは、どのディレクトリの中でクローンするかだけです。

- git configの`url`の設定を利用して、リポの中では自動的に目的のsshホストが使われるようにする。
- GitHooksでgit cloneのみをフックしてsshホストを自動的に書き込む。