シンクロ・フード エンジニアブログ

飲食店ドットコムを運営する、株式会社シンクロ・フードの技術ブログです

依存しあう複数の Git リポジトリを git subtree で monorepo 化する

基盤チームの川井 (@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 リポジトリは統合したリポジトリの分だけ大きくなります。

あまり見慣れない運用方法ではありますが、RailsBabelReact といった巨大な OSS プロジェクトでも採用されています。

monorepo へシームレスに移行する

monorepo に移行するとなると、各リポジトリのブランチも monorepo 側に移さなければいけません。 そこで、開発者の手を止めることなく monorepo に移行する手段として、以下の方法を検討しました。

  • 各リポジトリの feature ブランチを含めたブランチ全てを monorepo に移し、移行直後から monorepo 側の feature ブランチで開発する f:id:synchro-food:20180329222655p:plain
  • master ブランチだけを monorepo に移し、各リポジトリで開発中の feature ブランチは merge 後に移行する f:id:synchro-food:20180329222652p:plain

前者の場合、各開発者の手元の変更は 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 つとしてぜひ検討してみてください!


シンクロ・フードでは開発基盤設計に興味のあるエンジニアも募集しています。ご興味のある方は以下よりご連絡ください!