基盤チームの川井 (@fohte) です。
今回は、monorepo と呼ばれる複数のリポジトリを単一のリポジトリで管理する運用方法の紹介と、実際にその運用に切り替えた話をします。
弊社は GitHub Enterprise を使っており、Git Flow を独自に拡張した master, develop, feature ブランチの 3 種類からなる運用フローを採用しています。 今回はその環境を前提としてお話します。
monorepo とは?
monorepo は、複数リポジトリを単一のリポジトリで管理するという Git リポジトリの運用方法の一つです。
monorepo のメリットとしては以下の点が挙げられます。
- 密結合なリポジトリの運用が楽になる
- 複数リポジトリに横断した修正が簡単にできるようになる
例えば、あるリポジトリ A の変更に従って別のリポジトリ B も変更するとき、リポジトリが複数分かれているとそれぞれのリポジトリに commit が分散することになります。 これは単純に面倒ですし、何より commit 可読性も悪くなります。
また、このようなときはリポジトリごとに Pull Request を立てる必要がありますが、monorepo では 1 つにまとまるため、Pull Request の可読性も向上します。
反対に monorepo のデメリットとしては以下の点が挙げられます。
- リポジトリが巨大になる
複数に分散しているリポジトリを 1 つにまとめてしまうので、結果として Git リポジトリは統合したリポジトリの分だけ大きくなります。
あまり見慣れない運用方法ではありますが、Rails、Babel、React といった巨大な OSS プロジェクトでも採用されています。
monorepo へシームレスに移行する
monorepo に移行するとなると、各リポジトリのブランチも monorepo 側に移さなければいけません。 そこで、開発者の手を止めることなく monorepo に移行する手段として、以下の方法を検討しました。
- 各リポジトリの feature ブランチを含めたブランチ全てを monorepo に移し、移行直後から monorepo 側の feature ブランチで開発する
- master ブランチだけを monorepo に移し、各リポジトリで開発中の feature ブランチは merge 後に移行する
前者の場合、各開発者の手元の変更は monorepo 側に取り込まれないという欠点があるため、今回は後者の手段を選択しました。 また、弊社では master ブランチとは別に develop ブランチもありますが、develop ブランチも後述の方法で移行すると master と develop が全行差分になってしまう問題があったため、master ブランチ移行後に新規に master から develop ブランチを切り直しました。
移行手順
今回は git subtree
コマンドを用いて移行しています。
git subtree
自体は git に内蔵されているコマンドではないため、環境によってはコマンドが無く、その場合は手動でインストールする必要があります。
git clone https://github.com/git/git.git cd git/contrib/subtree make cp git-subtree /usr/local/bin # パスが通っているディレクトリにコピーする
各リポジトリの master ブランチを monorepo に移行する
先に monorepo 化する各リポジトリの remote を登録し、fetch しておきます。
git remote add <name> <repo_url> git fetch -a <name>
<name>
: remote の名前です。今回は monorepo のサブディレクトリ名とします。<repo_url>
: 各リポジトリの URL です。 (例:git@github.com:rails/rails.git
,https://github.com/rails/rails.git
)
また、空 commit を作成しておきます。
git checkout --orphan master git commit --allow-empty -m 'Root commit for master branch'
次に、git subtree add
コマンドで各リポジトリを monorepo のサブディレクトリとして追加します。
git subtree add --prefix=apps/<name> <name>/master
--prefix
に指定したディレクトリにリポジトリが取り込まれます。
今回は apps
ディレクトリ下にフラットにリポジトリを置いています。
最後に、念のため git diff-tree
で差分をチェックし、問題がなければ master ブランチの移行は完了です。
git diff-tree -r --stat <name>/master master:apps/<name>
feature ブランチの変更内容を monorepo に取り込む
今回は、各リポジトリで開発中の feature ブランチはそのリポジトリに merge してしまい、その後 monorepo 側に取り込みます。
その後、git subtree merge
コマンドでサブディレクトリに merge し、monorepo 側に各リポジトリでの変更を取り込みます。
git checkout master git subtree merge --prefix=apps/<name> <name>/<branch>
master ブランチ移行時同様に差分をチェックし、問題がなければ feature ブランチの変更の取り込みは完了です。
git diff-tree -r --stat <name>/master master:apps/<name>
所感
移行前に懸念していたこととして、上述のデメリットとしても挙げた Git リポジトリが重くなるかもしれないという点がありました。
今回はアクティブな Rails アプリケーションのみ (5, 6 リポジトリ) を monorepo 化し、commit の総数は 6,000 ほどとなりましたが、現時点では git コマンドに重さを感じることはありません。
ファイルが増えている分容量自体は大きくなっているため git clone
時などに重さを感じますが、これは複数リポジトリを git clone
しているようなものであるため許容しています。
また、移行の難易度やコストが高いのではという懸念もありましたが、想像以上に容易に移行することができ、全く問題はありませんでした。
monorepo 化して 1 ヶ月ほど運用されていますが、特に大きな問題もなく、1 リポジトリで完結することの恩恵を大いに受けて生産性が向上していると感じます。
まとめ
今回は monorepo という Git リポジトリの運用方法について紹介しました。
git subtree
コマンドを用いると手軽に monorepo 化できるので、密結合なリポジトリがあるときの解決策の 1 つとしてぜひ検討してみてください!
シンクロ・フードでは開発基盤設計に興味のあるエンジニアも募集しています。ご興味のある方は以下よりご連絡ください!