こんにちは。AI・機械学習チームの苅野です。
このブログはエムスリー Advent Calendar 2025 10 日目の記事です。API と Batch でリポジトリが分かれているプロダクトを 1 つのリポジトリに統合しようと考えて検証を行い、「既存リポジトリでgit mv してからmerge --allow-unrelated-histories する」 方法を採用することに決めました。
git filter-repo など他の手法も検証しましたが、この方法だと「Commit ID を変えずに(過去の PR リンク等を維持したまま)、--follow オプションでファイル移動前の履歴も追える」 ため、この方針で統合を進める予定です。
具体的な検証過程と手順を以下に紹介します。
今回リポジトリ統合を行いたいと考えたプロダクトはこれまで Batch と API でリポジトリを分けて管理していました。 BigQuery にデータをクエリし整形して Elastic Cloud に投入する Batch と、クライアントからリクエストを受けて Elastic Cloud に問い合わせて結果を返す API から成り立っています。
「モノレポ」というと全社のコードを 1 つにまとめる大規模なものを指すことがありますが、本記事で目指しているのはあくまで「密結合な関係にある API と Batch を 1 つのリポジトリで管理する(プロジェクト単位での集約)」状態です。
もちろんビルド時間の短縮や権限管理の分離といったリポジトリ分割(ポリレポ)ならではのメリットもあり全社的にモノレポを推進しているわけではなくあくまで適材適所だと考えていますが、今回のケースでは以下の理由からリポジトリを統合するメリットが大きいと判断しその方法を検証しました。

同一リポジトリであれば Batch, API 両方に対して同時に変更を加えることができます。例えば新しいインデックスを作成する場合に Batch では新しいインデックスとそのインデックスにドキュメントを投入し、API では新しいインデックスからもドキュメントを検索するコードを書いて 1 つの Pull Request にまとめることができます。
API の CI では integration_test が走るようになっています。このプロダクトでは定義ファイルから Elasticsearch のインデックス管理を行う eskeeper を利用しており、これまでは Batch リポジトリにある定義ファイルを API リポジトリにもコピーしてコミットし、テストで使用していました。Batch リポジトリの定義ファイルに変更を加えた場合 API リポジトリに忘れずにコピーする必要があり、定義ファイルを二重管理する必要があります。リポジトリを 1 つにすれば同一リポジトリの Batch ディレクトリに存在する定義ファイルを API の integration_test で利用できます。
我々は開発の効率化のため、プロジェクトの雛形を自動的に生成するプロジェクトテンプレートを利用しています。下記はAPI開発で利用している cookiecutter テンプレートについての記事ですが、gokart を使った Batch のテンプレートも存在します。
AI・機械学習チームでは数多くのプロダクトが稼働しており(なんと去年一年で 28 個リリースしました)、API と Batch をセットにした構成のプロダクトも増えて知見が溜まってきました。チームでの最新の知見は先述した cookiecutter テンプレートに反映されており、cruft を使って追従することができます。CI の関係で統合リポジトリ時の cruft 追従が面倒でしたが、知見が溜まってきて解消されてきました。
今回の API と Batch はこれまで別々のリポジトリとして開発されてきたため、それぞれの main ブランチには共通のコミット履歴(共通の祖先)が存在しません。
このように履歴のつながりがない(unrelatedな)ブランチ同士は通常マージできませんが、git merge コマンドに --allow-unrelated-histories オプションを付与することで、強制的に統合することができます。API リポジトリのコードを api/ に、Batch リポジトリのコードを batch/ に移動し、その後でマージすれば 1 つのリポジトリに集約にできます。
また今回は完全移行が目的で、移行後の履歴の追いやすさを優先しました。スパッと切り替えて以後は統合リポジトリのみを正とする(旧リポジトリはアーカイブする)想定です。
最初に考えたのは git subtree を使う方法です。git subtree を使うと git リポジトリの中で複数のリポジトリの履歴を管理できるので、API リポジトリに Batch リポジトリを取り込めるのではないかと考えました。
git clone<api repository> local-apicd local-apimkdir api# .gitと新しく作ったapi以外を移動gitmv$(ls -A|grep -v'^\.git$'|grep -v'^api$') api/git commit-m'move to api directory'git subtree add--prefix=batch<batch repository> main
この方法でも統合自体は可能です。しかし、subtree add した時点でファイルのパスが batch/ 配下に変わってしまうため、ファイル単位のコミット履歴(Blame)が途切れてしまうという課題がありました。
具体的には、普通にgit log batch/batch_job.py を実行しても統合以降の履歴しか表示されません。過去の履歴を見るにはgit log <batch repositoryの最新コミットID> -- <旧ファイル名>のように明示的に指定する必要がありシームレスに辿れないのが難点でした。
この「ファイルごとの履歴が断絶する問題」を解決するために、次は git filter-repo を検討しました。
次に考えたのはgit filter-repo を使う方法です。git filter-repo は git の履歴を書き換えるツールで、過去にサイズが大きいファイルをコミットしてしまった場合に履歴から取り除いたり、コミットメッセージを書き換えたりと色々なことができます。ドキュメントの使用例の中にまさしく今回やりたいファイルを別ディレクトリに移動するが含まれています。
moving all files into a subdirectory in preparation for merging with another repo
git filter-repo を使って API と Batch で別リポジトリに分かれていたのを統合してみましょう。
git clone<api repository> local-apicd local-api# すべてのファイルをapi/配下に移動(履歴も書き換え)git filter-repo--to-subdirectory-filter api# project ディレクトリに戻るcd ..# batch をクローンgit clone<batch repository> local-batchcd local-batch# すべてのファイルをbatch/配下に移動(履歴も書き換え)git filter-repo--to-subdirectory-filter batchcd ..# local-api で batch のマージを行うcd local-api# local-batch ディレクトリを remote に登録git remote add batch ../local-batchgit fetch batch# ここでローカルの batch をマージするgit merge batch/main--allow-unrelated-histories
上記の手順を踏むことで git の履歴を書き換えて API リポジトリは最初から api ディレクトリ配下で開発されてきたようにできます。実際統合した後にgit log --oneline api を実行すると統合前と同じコミットメッセージが表示され、GitHub の画面上でも各ファイルの履歴を辿ることができます。
一方でこの方法だと git の履歴を書き換えるためコミット ID が変わってしまいます。そのため今までコミット ID でリンクされていた場合リポジトリの URL を書き換えてもコミット ID が異なるので辿れなくなってしまいます。
最後に試したのは既存のコードを api/ や batch/ に移動するコミットを積んでからマージする方法です。
git clone<batch repository> local-batchcd local-batch# batch ディレクトリがないと git mv がエラーになるmkdir batch# .gitと新しく作ったbatch/以外を移動gitmv$(ls -A|grep -v'^\.git$'|grep -v'^batch$') batch/git commit-m'move to batch directory'cd ..git clone<api repository> local-apicd local-api# api ディレクトリがないと git mv がエラーになるmkdir api# .gitと新しく作ったapi以外を移動gitmv$(ls -A|grep -v'^\.git$'|grep -v'^api$') api/git commit-m'move to api directory'git remote add batch ../local-batchgit fetch batch# ここでローカルの batch をマージするgit merge batch/main--allow-unrelated-historiesgit remote remove batch
こうするとコミット ID を書き換えずに統合できます。git log --oneline api/README.md だと 'move to api directory' のように移動したコミットしか表示されませんが、--follow オプションをつけてgit log --oneline --follow api/README.md とするとファイルを移動する前の履歴も辿ってくれます。GitHub の画面上でもファイルごとの履歴を見ることができます。これで履歴を保ちながら 1 つのリポジトリに統合できました。
本記事では API と Batch でリポジトリが分かれているプロダクトを 1 つのリポジトリに統合する方法を紹介しました。履歴を保ちながらファイルごとの履歴も辿れる git mv してから git merge --allow-unrelated-histories する方法で統合しようと考えています。
AI・機械学習チームでは、医療現場やWebでの課題解決に取り組むエンジニアを募集しています。
「機械学習モデルの社会実装」や「MLOps基盤の構築・改善」に興味がある方、コスト意識を持った技術選定に関心のある方は、ぜひカジュアル面談でお話ししましょう!
引用をストックしました
引用するにはまずログインしてください
引用をストックできませんでした。再度お試しください
限定公開記事のため引用できません。