GitHub.com から GitHub Enterprise にリポジトリ移行する際の注意点

お仕事の private リポジトリについて、オトナの事情でワールドワイドウェブな GitHub.com からイントラネット内の GitHub Enterprise (以下 GH:E) に移行する必要があった際に読んだ文献、踏んだ地雷について記しておきます。

「分散 SCM の Git なんだから、upstream を変更して push すれば終わりでしょ？」という疑問に対しては、Pull Request や ISSUE もろもろを移行対象に含めているのがこの記事で取り扱う移行作業のポイントです。とりわけ息の長いプロジェクトでは大切な資産になるのでいい感じに移行しましょう。

## ざっくりいうと

* リポジトリ移行中のロックは git 操作だけでなく、ブラウザでの参照含めてすべてが操作不可になる
* リポジトリのアーカイヴファイルの中身は一度解凍してきちんと見ておきましょう
* インポート時にエラーが起きたときは ActiveModel::Serializers::JSON#from_json をイメージしたエスパー力が有効

## はじめに

[GitHub が公式に用意しているマニュアル](https://help.github.com/enterprise/2.4/admin/guides/migrations/)をベースにしています。
基本的にこれらのマニュアルからサクッとは読み取れなかったことを中心に書いておきます。

## リポジトリのエクスポートには Web API を叩くアクセストークンが必要

リポジトリのエクスポートは GitHub に対する Web API 経由で行います。
Web API を叩くための事前準備としてに、[GitHub にログインしてアクセストークンを作成しておく](https://github.com/settings/tokens)必要があります。
[こちらに書かれている](https://help.github.com/enterprise/2.4/admin/guides/migrations/preparing-the-github-com-source-organization/)とおりに行えば、ここではあまりハマりどころはないと思います。

## GitHub.com からのエクスポートの Web API を叩く

[こちらに書かれている](https://help.github.com/enterprise/2.4/admin/guides/migrations/exporting-the-github-com-organization-s-repositories/)手順を行えば、リポジトリのアーカイヴファイルを入手できます。

エクスポートで叩く Web API は 3 つ、最後のオプションを含めると 4 つです。

1. エクスポートするリポジトリを指定してアーカイヴをはじめる API
2. アーカイヴの進捗を確認する API
3. アーカイヴファイルを取得する API
4. アーカイヴファイルを削除する API (必須ではなくオプション扱い。7日後に自動で消えるらしい)

注意すべき API は最初のエクポートをはじめる API です。この最初に実行する API で返ってくる JSON に含まれるトップレベルの id は一連のエクスポート作業で必要になるものなので、ターミナルから流れて失われないように注意してください。

さらにより注意すべき点が以下です。

### リポジトリのエクスポート時のロック

GH:E への移行作業中に、新たに Pull Request が作られたりしたら移行対象漏れが起きて困りますね。

アーカイヴをはじめる API には、リポジトリの指定の他にいくつかのオプションがあり、そのひとつが `"lock_repositories":true` オプションを使ったリポジトリのロックです。移行文書にも以下のように記されており強く推奨されています。

> If you want to lock the repositories before migrating them, make sure lock_repositories is set to true. This is highly recommended.

ここでひとつめの地雷です。
リポジトリのロックは `git push` や、新たな ISSUE の作成といった書き込みを伴うものだけができなくなる? __残念だったな、誰がそんなことを言った。__

まず、アーカイヴ対象にしていた、GitHub 上のリポジトリが Web ブラウザ上で一切見れなくなります。__GitHub が落ちていて業務が止まった。困るわー。__ というものを半期に一回くらい見ている気がしますが、ちょうどあれの状態。ついでに書いておくと `git fetch` の類いも受け付けません。
移行作業の担当でないメンバーは、その時間帯はローカルリポジトリで開発を進めるなり、打ち合わせを入れておくなり、コーヒーを飲みに行くなりしましょう。

ちなみに、GitHub リポジトリの容量は[こちら](https://github.com/settings/repositories)で確認することができて、100MB くらいのリポジトリのアーカイヴにおよそ 30分〜1時間ちょっとかかりました。素振りを含めて何度か行ったのですが、アーカイヴにかかる時間にばらつきがあるようなので余裕を持って移行時間帯をアナウンスしておくと良いです ([公式文書にも最初の手順に書いてあって](https://help.github.com/enterprise/2.4/admin/guides/migrations/exporting-the-github-com-organization-s-repositories/) GitHub さんさすが) 。
あと、`"lock_repositories":false` としてリポジトリをロックせずに__素振り__をしておくとアーカイヴに掛かる見積りの標本にはなると思います。チームに根拠を伝えたり、捺印ビリティを取る際の情報に有効だと思います。

間違ってもロックに掛かる時間が __git clone に少し毛が生えた程度の時間__などと思わないことです。

### やっちまった！意図せずリポジトリをロックしてしまった！

人間なのでミスはつきものです。落ち着いてリポジトリのロックを解除しましょう。

[リポジトリのアンロックはこちらの文書](https://developer.github.com/v3/migration/migrations/#unlock-a-repository)に記されています。

公式文書だと若干 snippet 感があるため、本記事の執筆時点で有効な API をシェルスクリプトとして記しておきます。他の API と同じくアクセストークンや id などが API を実行するために必要です。

```sh
GITHUB_ACCESS_TOKEN='your_github_access_token'
ID='your_migration_id'
ORGONAME='your_org_name'
REPONAME='your_repo_name'

curl -H "Authorization: token $GITHUB_ACCESS_TOKEN" -X DELETE \
-H "Accept: application/vnd.github.wyandotte-preview+json" \
https://api.github.com/orgs/$ORGNAME/migrations/$ID/repos/$REPONAME/lock
```

ちなみにアーカイヴが終わっても__自動でロックが解除されることはありません__。移行先にインポートしていないときに、移行元に新しく Pull Request が生成されたりしても困るので、それはそうですねといった振る舞いになっています。

## リポジトリのアーカイヴファイルの中身

GitHub.com の Web API 経由で取得した tar.gz 形式のアーカイヴファイルを解凍すると以下のような構成になっています。ここではダウンロードしたアーカイヴファイルの名前を migration_archive.tar.gz としています。

migration_archive.tar.gz
├──attachments
├──attachments_000001.json
├──commit_comments_000001.json
├──issue_comments_000001.json
├──issue_events_000001.json
├──issues_000001.json
├──organizations_000001.json
├──pull_request_review_comments_000001.json
├──pull_requests_000001.json
├──repositories
├──repositories_000001.json
├──schema.json
├──teams_000001.json
└──users_000001.json

基本的にアーカイヴを開始する際に指定したリポジトリのソースコードやリポジトリ情報が連番形式の JSON 形式で入っているわけですが、ざっくりと中は見ておいた方が良いです。なぜかというと、この後の GH:E へのインポート時にエラーが起きたときに__勘が働く__ようになるためです (おまえはいったい何を言っているんだという理由は後述) 。
__特に受託開発なんかである気がするのですが、企業をまたいでリポジトリを移行するときは企業内情報が混ざっていないか__の観点も含めて見ておくと良いです。

### 特に見ておくと良い JSON

大きく以下の3つは、移行して良い情報かどうか見ておくと良いです。

* organizations_*.json
* teams_*.json
* users_*.json

移行するアクセストークンの作りによるかもしれませんが、中身としては移行元の organization にあるユーザーやチームの情報がガツッと入っているため、移行先に持って行くべき情報かどうか取捨選択が必要かもしれません。

## GH:E へのリポジトリのインポート

インポートの手順自体は[こちらの公式文書](https://help.github.com/enterprise/2.4/admin/guides/migrations/importing-migration-data-to-github-enterprise/)にあるとおり、ssh でログインした先の GH:E で、指定された `ghe-migrator` コマンドを実行して行きます。

最初に移行データに衝突がないかチェックを行うことからはじめることになります。
データに衝突があれば逐次解決をしていくか、そもそも衝突が起きないように JSON を用意しておくといった手段が考えられると思います (私が行った際は後者を採りました) 。

### Don't Panic

データの衝突がないことを確認したのち、インポートを実施した際にエラーが起きると、この Don't Panic メッセージを伴ってバックトレースが表示されるわけですが、正直バックトレースだけからエラーを追うことは困難です。
私の場合は、「アーカイヴファイルに含まれている JSON について、ActiveModel::Serializers::JSON#from_json をもとに RDB に復元しようとした際に、この関連が足りていないのでは。。。」などといったエスパー力を使ってセイヤーと進めました。

本題と全然関係ないですが、名著『達人プログラマー』にも記されていることで有名な、Don't Panic という言葉を選ぶあたり `ghe-migrator` コマンドの作者はセンスいいですね。

### JSON の依存関係?

私が行おうとしたケースは、企業間でのマイグレーションだったため、organizations_*.json, teams_*.json, users_*.json をごそっと削除して、アーカイヴしなおしてのトライといったものでした。
これらのうち、仮データでも organizations_*.json と users_*.json がないと repositories_000001.json などからデータを生成する際に association が作れないためエラーになるようです。泥臭い手談であれば、移行先の GH:E に新たな oarganization を追加したものをエクスポートして、アーカイヴを組み立てるなど工夫を施すと良いかなと思います (たぶんもっとクールな方法はありそう) 。

### ユーザーの紐付け

GitHub.com のアカウントと GH:E のユーザーのマッピングは email 情報をもとに行われているようです。自分のアカウントとの紐付けがないメンバーがいる場合は、移行後にでも GH:E 側でメールアドレスの編集で紐付けを行うように伝えておくと良いです。

## まとめ

GitHub のリポジトリの移行に対するサポートの仕組みはよく出来ていると思います。
ソースコードだけなく、Pull Request や ISSUE は開発プロセスの成果なので、リポジトリを移行することになった際にはきちんと持って行くことをおススメします。