GitHubにリポジトリを移行する

# 概要

オンプレや別のホスティングサービスなどで運用していたGitリポジトリをGitHubに移行するための手順です。コミット履歴・ブランチ・タグなどを全て維持したままGitHubへ移行します。既存のGitHubリポジトリの複製を作りたい場合にも使えます。

# 移行手順

### Step1. リモートリポジトリの移行

移行元のリモートリポジトリをミラークローンします。

```shell
git clone --mirror ＜移行元のリモートリポジトリ＞
```

あらかじめGitHubに移行先の空リポジトリを作っておき、そこへ向けてミラープッシュします。

```shell
$ cd ＜クローンした移行元のgitディレクトリ＞
$ git push --mirror ＜移行先のGitHubリポジトリ＞
```

もしここでつまづいた場合は後述のトラブルシュートが参考になるかもしれません。
リモートリポジトリの移行は基本これだけで完了です。

ただし、移行元でGit LFSを利用している場合は実体ファイルが別のストレージに保管されているはずなのでその移行も必要です。以下の手順でfetch⇒pushしてLFS管理のファイルを移行先に移します。

```shell:LFSを利用している場合のみ
git lfs fetch --all
git lfs push --all ＜移行先のGitHubリポジトリ＞
```


### Step2. ローカルの作業リポジトリの向き先変更

移行先のリポジトリから新しくクローンして一からローカル環境を構築し直す場合にはこの手順はいりませんが、既にローカル環境に旧リポジトリのクローンと作業環境がある場合は、以下の手順でリポジトリのoriginを移行先に変更する必要があります。複数人で開発している場合は各自で設定してもらいましょう。

まず現在のリモートURLを確認します。

```shell
git remote -v

# 実行結果
# origin  ＜移行元のリモートリポジトリ＞ (fetch)
# origin  ＜移行元のリモートリポジトリ＞ (push)
```

移行元の旧リポジトリを参照していることを確認した上で、これを移行先の新リポジトリに変更します。

```shell
git remote set-url origin ＜移行先のGitHubリポジトリ＞
```

もう一度向き先を確認します。

```shell
git remote -v

# 実行結果
# origin  ＜移行先のGitHubリポジトリ＞ (fetch)
# origin  ＜移行先のGitHubリポジトリ＞ (push)
```

これでfetch、pushとも移行先を参照するように変わっているはずですが、もしpushの参照が変わっていなければ`--push`オプションを付けて再度実行します。

```shell
git remote set-url --push origin ＜移行先のGitHubリポジトリ＞
```

# トラブルシュート

### Q1. GitHubの認証ではじかれます

git pushする時に聞かれるGitHubのパスワードは、普段WEBからアクセスする時に使っているものとは違います。パーソナルアクセストークンをパスワードとして使いましょう。

### Q2. ミラープッシュ後に元のリポジトリを更新してしまいました

移行元の更新情報を取得して再度ミラープッシュします。
ただし、移行先で既に別の更新をかけている場合はその更新分は失われるので注意しましょう。
可能なら移行後は旧リポジトリは破棄してしまうか、書き込みできないようにおくのがいいと思います。

```shell
git fetch origin
git push --mirror ＜移行先のGitHubリポジトリ＞
```

### Q3. プッシュが失敗します

`fatal: Could not read from remote repository` といったエラーが出てプッシュに失敗する場合、GitHubの制限に引っかかっている可能性があります。GitHubでは一度にプッシュできるサイズは2GBまでとなっているので、巨大なリポジトリを移行しようと思うとこの制限に引っかかります。そこで、[公式のトラブルシューティング](https://docs.github.com/ja/get-started/using-git/troubleshooting-the-2-gb-push-limit)を参考に、一度にまとめてプッシュするのではなく分割してプッシュするようにします。

まず、メインブランチ（ここではブランチ名をmainとします）のコミット履歴を古い順に1000コミットごとに表示します。

```shell
git log --oneline --reverse refs/heads/main | awk 'NR % 1000 == 0'
```

```
d98d6c572 コミットメッセージ...
6579c5c03 コミットメッセージ...
ebab9ff92 コミットメッセージ...
c71b883c2 コミットメッセージ...
4eaa511c4 コミットメッセージ...
5c92a9179 コミットメッセージ...
1674f9fa2 コミットメッセージ...
...
```

これを上から順番にプッシュしていきます。
つまり古い方から1000コミットずつ分割してプッシュすることになります。
（何コミットずつに分割するかは適宜数字を調整してください）

```shell
git push ＜移行先のGitHubリポジトリ＞ ＜コミットハッシュ＞:refs/heads/main
```

これを繰り返すことで指定したコミットハッシュまでのコミットを小分けにして順次プッシュしていくことができます。律義に一つずつ全部やる必要もないので、もう少し幅を広くしても制限に引っかかりそうでなければ途中をいくつか飛ばしてもいいです。

ある程度までプッシュできたら、もう一度全体をミラープッシュして残りをプッシュできるか確認します。
```shell
git push --mirror ＜移行先のリモートリポジトリ＞
```

成功すれば、残りのコミット履歴・他のブランチ・タグなどもそのまま移行ができているはずです。GitHub上から確認してみましょう。

ここでまだエラーが出るようであればさらに分割する必要があります。もしメインブランチの分割だけでは足りないようであれば、メインより先に進んでいる他のブランチも同様に分割プッシュします。

それでもプッシュできない場合、主に以下が理由として考えられます。

- 1コミットで2GBを超えている
    - ⇒ rebaseで履歴を書き換えてそのコミット自体を分割する
- 100MBを超えるファイルをプッシュしようとしている
    - ⇒ GitHubでは1ファイル100MB制限があるのでGit LFSを使う
- Git LFSのストレージ容量を超えている
    - ⇒ 追加パックを購入（1パックあたり50Gで$5/月）

これでもプッシュできない場合は思い切って履歴を削除するか、新しく作り直すことも検討してもいいかもしれません。

# 移行を決める前に・・・

最後に、本題から逸れますが企業用途でGitHubに移行するにはデメリットもあるのでそこは事前に検討してから移行に踏み切りましょう。場合によってはGitHubをオンプレ環境で利用できるGitHub Enterprise Serverも選択肢に入ってくるかもしれません。（費用感と運用コストさえ合えば）

### 容量の問題

トラブルシュートでも触れましたが、GitHubにはファイル、プッシュ、リポジトリに対するサイズ制限がそれぞれあります。1ファイルあたりのサイズは100MB以下、1度にプッシュできるサイズは2GB以下、1リポジトリあたりのサイズは1GB～5GB以下を推奨しています。
1ファイルあたり100MB制限については、Git LFSを利用すれば回避できます。2GBのプッシュ制限も実際の運用ではそれほど問題にはならないでしょう。差し当たって問題になりそうなのはリポジトリサイズでしょうか。ここでいうリポジトリサイズは単なるコードベースのサイズではない点に注意が必要です。コードベースがスリムであっても、歴史の長いリポジトリで過去のgitの履歴が膨れ上がっている場合はサイズオーバーする可能性があります。履歴の削除、リポジトリの分割、Git LFSの併用などの対応が必要になるかもしれません。

### セキュリティと権限の問題

GitHubに限らずですが、特に企業利用でソースコードをホスティングする場合はセキュリティと権限の問題が無視できません。2FAの義務化や、メンバーに対するリポジトリのアクセスポリシーの設定、ブランチの保護ルールなど、GitHubの機能を使って望むレベルのコード保護ができるのかどうかは事前に検討しておきましょう。また、センシティブなデータを含むファイルをリポジトリに含めない、といった基本的な運用ルールも必要です。

### 可用性の問題

これも個人利用か企業利用かによって重要度が異なってくると思いますが、GitHubもSaaSである以上、障害の発生は想定しておかなければなりません。運用的にどれくらいのダウンタイムであれば目を瞑れるのか、仮に大規模障害が長時間発生した時でも影響を最小限にして業務を継続できるのかは考えておく必要があります。