結論
React 製の管理画面と Laravel 製のAPIを、別々のGitリポジトリで管理していました。
これを git subtree で1つのリポジトリ(モノレポ)にまとめました。履歴を捨てずに、--squash なしで統合しています。
作業自体は2分41秒で終わりました。ただ、統合後に想定と違ったことがあります。
- 履歴は保持されている(統合元の root commit がそのまま残っている)
- しかし
git log -- <現在のパス>では統合前まで遡れなかった -
--followを付けても--full-historyを付けても結果は変わらなかった - 実際に試したところ、3つの方法で追えた
この記事では、実際に使ったコマンドと、統合後に履歴を追う方法をまとめます。
あわせて、統合から約4週間後に見直して見つかった「移行の片付け残し」も載せます。
記事中の値について
コマンド例に出てくる以下は、今回実際に使った値です。自分の環境に読み替えてください。
| 記事中の値 | 意味 |
|---|---|
portfolio-react-admin / portfolio-laravel-api
|
統合元リポジトリ名 |
frontend / backend
|
統合後のディレクトリ名 |
react / laravel
|
統合時に登録したremote名 |
943ecad / d24a5e6 / 257646d / e50dabc など |
実際のコミットSHA |
ローカルの絶対パスは /path/to/... に、Gitのauthor名とメールアドレスは Developer <redacted> に置き換えています。
1. 実際に実行したコマンド
これだけです。
# モノレポの器を作る
git init
git commit --allow-empty -m "Initial commit"
# 1つ目(React側)を取り込む
git remote add react /path/to/portfolio-react-admin
git fetch react
git subtree add --prefix=portfolio-react-admin react main
# 2つ目(Laravel側)を取り込む
git remote add laravel /path/to/portfolio-laravel-api
git fetch laravel
git subtree add --prefix=portfolio-laravel-api laravel main
git subtree add の形は次のとおりです。
git subtree add --prefix=<配置するディレクトリ> <remote名> <ブランチ名>
統合元はローカルパスでもよい
今回の2つのリポジトリは当時GitHubへpushしておらず、ローカルにしか存在しませんでした。
git remote add <名前> /path/to/<リポジトリ> でローカルパスを登録し、git fetch と git subtree add を実行しています。今回はこの手順でそのまま動きました。
git subtree add <URL> <branch> のようにURLを直接渡す形もありますが、今回は remote登録 → fetch → subtree add の手順を踏んでいます。
取り込み後に frontend / backend へリネームした
今回は統合前のリポジトリ名(portfolio-react-admin / portfolio-laravel-api)をそのまま prefix に指定し、その後 frontend / backend へリネームしました。
$ git show --stat 257646d | tail -1
263 files changed, 0 insertions(+), 0 deletions(-)
257646d では 263 files changed, 0 insertions(+), 0 deletions(-) となっており、今回のリポジトリではファイル内容を変更せずに移行できています。
2. --squash を使わなかった理由
git subtree add には --squash オプションがあります。
付けると、取り込んだ側の履歴が1コミットに潰れます。
| コミットメッセージ | 統合元の履歴 | |
|---|---|---|
--squash なし |
Add '<dir>/' from commit '<sha>' |
すべて残る |
--squash あり |
Squashed '<dir>/' content from commit '<sha>' |
残らない |
今回の要件は「既存のGit履歴を保持したままモノレポ化する」だったので、付けていません。
コミットメッセージを見れば、どちらで取り込んだか後から判別できます。
3. 統合コミットに残る情報
git subtree add は、マージコミットにトレーラーを残してくれます。
$ git log -1 --format="%s%n%n%b" d24a5e6
Add 'portfolio-laravel-api/' from commit '53e181a2865ad5e44770d2fced54d8d4f7f664b2'
git-subtree-dir: portfolio-laravel-api
git-subtree-mainline: 943ecad515b27339fa1a7d854a49e745bf1c6aec
git-subtree-split: 53e181a2865ad5e44770d2fced54d8d4f7f664b2
| トレーラー | 意味 |
|---|---|
git-subtree-dir |
取り込み先のディレクトリ(prefix) |
git-subtree-mainline |
取り込み前のモノレポ側のコミット(マージの第1親) |
git-subtree-split |
取り込んだ側のコミット(マージの第2親) |
git-subtree-split の値は後で使います(→ 6章の方法B)。
4. 履歴が保持されていることの確認
root commit が複数あるか
$ git rev-list --max-parents=0 HEAD | while read c; do git log -1 --format="%h %ad %s" --date=short $c; done
005c408 2026-07-17 Initial commit # モノレポの器
e50dabc 2026-04-15 Laravel13初期構築 # Laravel側の起点
b438d8f 2026-03-05 初期構成 # React側の起点
独立した3つの履歴の起点が、1つのリポジトリに同居しています。
コミット数が合うか
$ git rev-list --count main
216
内訳は 1(器の初回コミット)+ 121(React側)+ 31(Laravel側)+ 2(統合マージ)+ 61(統合後)。
(この値は調査した2026年8月12日時点のものです。その後もコミットは増えています)
統合元それぞれのコミット数は、git-subtree-split のSHAを起点にすれば確認できます。
$ git rev-list --count 8bc29f8 # 121 (React側)
$ git rev-list --count 53e181a # 31 (Laravel側)
ここまでは期待どおりでした。
5. git log -- <path> では追えなかった
統合から約4週間後、backend/app/Models/User.php の履歴を見ようとしたときです。
$ git log --oneline -- backend/app/Models/User.php
257646d refactor: rename project directories
1件だけ。 ディレクトリをリネームしたコミットで止まります。
リネームを追う --follow を付けます。
$ git log --follow --oneline -- backend/app/Models/User.php
257646d refactor: rename project directories
変わりません。履歴の簡略化を無効にする --full-history も試します。
$ git log --full-history --oneline -- backend/app/Models/User.php
257646d refactor: rename project directories
やはり1件。frontend 側も同じでした。
$ git log --oneline -- frontend/src/App.tsx
257646d refactor: rename project directories
履歴自体は残っている
誤解しやすいところなので明記しておきます。
コミット履歴は保持されています。 root commit も3つ残っていますし、git log(パス指定なし)を打てば統合前のコミットもすべて出てきます。
追えないのは、現在のパスを指定した git log です。
git subtree add は取り込んだファイルを prefix 配下に置くので、統合の前後でパスが変わります。
app/Models/User.php
→ portfolio-laravel-api/app/Models/User.php (subtree add)
→ backend/app/Models/User.php (ディレクトリリネーム)
内部の挙動として断定できるほどは検証していないので、この記事では**「実際にこうなった」「こうすれば追える」**までにとどめます。
6. 履歴を追跡する3つの方法
実際に試して機能した方法です。
方法A: グロブpathspec を使う(一番手軽)
パスの先頭が変わっているだけなので、* で受けます。
$ git log --oneline -- '*app/Models/User.php'
257646d refactor: rename project directories
d24a5e6 Add 'portfolio-laravel-api/' from commit '53e181a2865ad5e44770d2fced54d8d4f7f664b2'
4b98cbf 権限管理(admin/user)
f30800d ユーザーの新規登録でパスワード入力欄を追加
6ce072c 認証を追加(React:localStorage、Laravel:Session)
76d2c08 Docker/MySQLを導入
e50dabc Laravel13初期構築
7件。root commit の e50dabc まで戻りました。
frontend 側も同様です。
$ git log --oneline -- '*src/App.tsx' | wc -l
21
日常的にはこれが一番手軽です。
方法B: git-subtree-split のSHAを起点にする
3章で見たトレーラーの値をそのまま使います。
$ git log --oneline 53e181a | wc -l
31
$ git log --oneline 53e181a | head -3
53e181a refactor: Rename repository to portfolio-laravel-api
e06a854 refactor: Rename repository to laravel-portfolio-api
c38d545 docs: Add comprehensive README for Laravel API backend
統合元リポジトリの履歴を、当時のパス構成のまま読めます。「統合前はどうなっていたか」を確認したいときはこちらが正確です。
方法C: git blame を使う
今回のリポジトリでは、git blame で統合前まで遡れました。
$ git blame -L 1,6 --date=short backend/app/Models/User.php
^e50dabc app/Models/User.php (Developer 2026-04-15 1) <?php
^e50dabc app/Models/User.php (Developer 2026-04-15 2)
^e50dabc app/Models/User.php (Developer 2026-04-15 3) namespace App\Models;
^e50dabc app/Models/User.php (Developer 2026-04-15 4)
^e50dabc app/Models/User.php (Developer 2026-04-15 5) // use Illuminate\Contracts\Auth\MustVerifyEmail;
^e50dabc app/Models/User.php (Developer 2026-04-15 6) use Database\Factories\UserFactory;
e50dabc は Laravel側リポジトリの最初のコミットです。しかも括弧の中に、統合前の旧パス app/Models/User.php が表示されています。
blame はディレクトリ一括リネームも subtree のマージも越えて遡れているわけです。行単位で「誰が・いつ・どのコミットで」書いたかを知りたいときは、そもそも blame で足ります。
使い分け
| やりたいこと | 使う方法 |
|---|---|
| ファイルの変更履歴をざっと見たい | 方法A(グロブpathspec) |
| 統合前のリポジトリ構成のまま履歴を読みたい | 方法B(split SHA) |
| 特定の行がいつ入ったか知りたい | 方法C(git blame) |
7. GitHub側にリポジトリを作るときの注意
ローカルで統合を終えた後、GitHubへ反映するところで詰まりました。
GitHub上でリポジトリを作成すると、README等を付けた場合に初回コミットが生成されます。
$ git log -1 --format="parents: [%p]%nsubject: %s" f2417484
parents: []
subject: Initial commit
parents: [] —— 親を持たない root commit です。
ローカル側の Initial commit も root commit なので、この時点でまったく別系統の履歴が2本存在することになります。共通の祖先がないため、そのままでは push が通りません。
$ git merge-base --is-ancestor f2417484 main; echo $?
1 # 祖先ではない
最終的に origin/main は非fast-forwardで更新されました。ただし当時の操作手順は正確に覚えておらず、ローカルのreflogにも update by push としか記録されないため、具体的にどのコマンドで解決したかは断定できません。
次に同じことをするなら
今なら、GitHub側は README・.gitignore・ライセンスを何も付けずに空で作るか、先にGitHubで作ってから git clone して中身を入れます。
これは git subtree 固有の問題ではありません。「ローカルとGitHubの両方でリポジトリを初期化してしまった」という、よくある事故です。
8. Tips: remote を消すと reflog も消える
統合に使った react / laravel remote が不要になったので削除しました。
git remote remove react
git remote remove laravel
削除後、2026-07-17付の reflog エントリが 21件から10件に減りました(実測)。git remote remove は remote-tracking ref とその reflog も削除するためです。
コミット履歴が消えたわけではありません。 消えたのは remote-tracking ref に紐づくローカルの作業記録(git fetch を実行した時刻など)です。
後から調査する可能性があるなら、削除前に保存しておいてください。
git reflog --all --date=iso > migration-reflog.txt
9. モノレポ化後のチェック項目
統合から約4週間後に見直したところ、旧構成由来の残骸が4件見つかりました。実際に確認・対応した内容をチェックリストにします。
履歴を追えるか
-
git rev-list --max-parents=0 HEADで統合元の root commit が残っているか -
グロブpathspec / split SHA /
git blameのどれかで、目的のファイルの履歴を追えるか -
git-subtree-splitのSHAを記録したか
ルートに置くべきファイル
-
ルートに
.gitignoreがあるか
今回はここが抜けていました。統合元から持ち込まれた frontend/.gitignore と backend/.gitignore はサブディレクトリでもそれぞれ有効に働くため、動いてしまって気づけませんでした。ルート直下の .DS_Store が対象外になっていたので、ルートに .gitignore を追加しました。
重複した設定ファイル
- 同名の設定ファイルが複数の階層に残っていないか
今回は docker-compose.yml がルートと backend/ の2か所にありました。backend/docker-compose.yml は旧Laravel側リポジトリのものです。
削除前に次を確認しました。
- READMEはルートの
docker-compose.yml前提で書かれている - リポジトリ内に
backend/docker-compose.ymlを参照する記述がない -
cd backendして Docker Compose を使う手順もない
モノレポでは同名の設定ファイルが複数の階層に存在しうるので、「使われていないように見える」だけで消すのは危ないと思います。
配置場所に意味がある設定
-
.github/配下などの設定が、意図した場所で有効になっているか
今回は、統合元の .github/skills/... が frontend/.github/ 配下に入ったまま残っていました。後日ルート側に同じ内容のファイルを作り直しており、diff -u で比較したら完全一致だったので、埋没した側を削除しました。
.github/ 配下の設定やエディタ・CI関連の設定などには、配置場所やワークスペースルートを前提に動くものがあります。 階層が変わったら、「ファイルが存在するか」だけでなく「その場所で実際に有効か」まで確認してください。
.gitignore のように移動しても効くものと、そうでないものが混在するのが厄介なところです。
エディタ / formatter 設定
- Lint・整形の設定が、新しい階層で有効か
今回、VS Code の Prettier・整形設定が期待どおり効かなくなりました。設定ファイルがすべてサブディレクトリ配下にあるためです。
$ git ls-files | grep -iE "prettier|eslint|pint|editorconfig"
backend/.editorconfig
backend/pint.json
frontend/.eslintignore
frontend/.eslintrc.json
frontend/.prettierignore
frontend/.prettierrc
以前はリポジトリ直下にあった .eslintrc.json が、モノレポのルートから見ると1階層下がります。現在のワークスペース設定には次の記述が入っています。
"eslint.workingDirectories": [
{ "directory": "./frontend", "changeProcessCWD": true }
]
なお .vscode/ をGit管理していなかったため、いつ何が変わったのかは追えませんでした。教訓としては、こういう開発環境の設定こそGit管理しておけばよかったという話でもあります。
CI
-
各ジョブに
working-directoryを指定したか - キャッシュキーをサブディレクトリごとに分けたか
jobs:
frontend:
steps:
- uses: actions/setup-node@v5
with:
cache-dependency-path: frontend/package-lock.json
- working-directory: frontend
run: npm ci
backend:
steps:
- uses: actions/cache@v5
with:
key: ${{ runner.os }}-composer-${{ hashFiles('backend/composer.lock') }}
- working-directory: backend
run: composer install --no-interaction --prefer-dist
今回は paths フィルタを入れていないので、frontend だけ触っても backend のジョブが走ります。
README
- ルートREADMEだけで全体像とセットアップ手順が分かるか
今回のモノレポ化の目的がこれでした。統合直後は README がルート・frontend/・backend/ の3つあり、1つに集約するまで9日かかっています。
単純に連結したのではなく、重複を削り、セットアップ手順をルートだけで完結させ、技術スタックをプロジェクト全体として書き直しました。
統合作業が2分41秒だったのに対して、READMEの再構成は9日間。 ファイルを1つのリポジトリに入れた時点では、目的は何も達成されていませんでした。
remote
- 統合に使った remote が残っていないか
- 消す前に作業記録を保存したか(→ 8章)
$ git remote -v
まとめ
git subtree で履歴を残して統合すること自体は簡単です。コマンドは数行で、今回は2分41秒で終わりました。
ただ、確認しておいたほうがよいことが2つあります。
1. 統合後の履歴の追跡性
履歴は保持されますが、git log -- <現在のパス> からは統合前まで辿れません(--follow / --full-history でも同じ)。グロブpathspec・git-subtree-split のSHA・git blame の3つで追えます。
2. ルート前提の設定の棚卸し
.gitignore、重複した Compose 設定、.github/ 配下、エディタ設定、CI、README、remote。今回は移行から約4週間後に見直して4件見つかりました。
移行は、ファイルを1つのリポジトリに入れた時点では終わりません。
題材にしたリポジトリは公開しているので、記事中のコミットSHAをそのまま git log や git blame で追試できます。ただし7章の f2417484 だけは例外です。現在の main に含まれていないため、cloneした環境からは参照できません。
React / TypeScript / Laravel を中心としたポートフォリオや、これまでの経験・スキルについては以下にまとめています。
移行から約4週間後に再調査して分かったことまで含めた、詳しい検証の記録はZenn版でまとめています。