同じリポジトリを 4 つのフォルダで開いて、作業を分ける。main は合流とビルドだけ、UE は同時に 1 本
One repository opened in four folders to split the work: main only merges and builds, one UE at a time
D32-3-T63-git-worktree-lanes・技術・日本語入力でプレイする仕組み
一つのリポジトリを、4 つのフォルダで同時に開いています。
フォルダごとに別のブランチが出ていて、作業もフォルダごとに分けます。
並列に作るための仕組みですが、この PC で本当に同時に動かせるのは 2 本までです。
この回は、何をどう分け、どこが詰まるかの話です。
この回の位置と、前後の回#
区切り: T58〜T64 作り方と周辺(7 回のうち 6 回目)
先に読むとよい回: T27(C++ の核を三つの外側で共有する)・T52(古い焼き物が回帰を隠す)
次に読む回: T64(AI と一緒に作る)
名前 — git worktree#
- 正式名
- git worktree(linked worktree・リンクした作業ツリー)
- 初出
- Git 2.5(2015 年 7 月)。GitHub のブログの告知は 2015-07-29。当時は experimental と書かれていた
- 組で使う名前
- fast-forward merge(早送りの合流)
- 近い名前
- trunk-based development(短命なブランチで、主の線へ頻繁に入れる進め方)
git worktree とは#
git worktree(リンクした作業ツリー)とは、一つのリポジトリに、作業ツリー(ファイルを展開したフォルダ)を何本も持たせる機能です。
git worktree add -b hotfix ../hotfix origin/master
GitHub の告知の例です。
「隣のフォルダ hotfix に、origin/master から新しいブランチ hotfix を出す」という意味になります。
ふつうの clone を 2 つ作るのと違うのは、中身を共有する所です。
- オブジェクト(コミット・ファイルの中身)と ref(ブランチ)は 1 か所。リンクした側の .git はフォルダではなくファイルで、本体の .git の中を指すだけ
- 作業ツリー・インデックス(ステージ)・HEAD は、フォルダごとに別
- 同じブランチを 2 つのフォルダで同時に出すことはできない
なので、フォルダの間でコミットを渡すのに push も fetch も要りません。
片方でコミットすれば、もう片方から、そのブランチがすぐ見えます。
よく使われるのは、作業の途中で急ぎの修正が来たときです。
stash してブランチを切り替える代わりに、別のフォルダを出して直す。
長いビルドやテストを片方で回しながら、もう片方で書く、という使い方もあります。
よく知られた注意は二つです。
一つは、追跡していないファイル(ビルドの生成物・依存の入ったフォルダ・ローカルの設定)がフォルダごとに別になること。
もう一つは、全部のフォルダで共有されるものがあること(設定の .git/config、stash の山、フック)。
うちの 4 本#
今日 git worktree list を読むと、4 本です。
kaedevn main 統合(合流・push・パッケージ・文書と、合流の後の UE のビルドとテスト)
kaedevn-adv feat/kaede-adv-proto ADV-CLI(ADV の本体。台本・照合・判定。CLI まで)
kaedevn-alchemist feat/kaede-alchemist-idle ADV-UE(ADV の描画・Slate・外殻)
kaedevn-rpg feat/kaede-map-rpg 撮影所(UE で背景を撮る道具とエディタ)
ブランチの名前と中身がずれています。
alchemist は凍結中の放置ゲームの名前で、その worktree を ADV の UE 側に転用しました。
rpg もマップの名前のままで、中身はエディタと撮影所の道具です。
ブランチを切り直す手間に見合わないので、名前はそのままにしています。
この原稿を書いている worktree の .git も、中身は 1 行です。
gitdir: <本体のフォルダ>/.git/worktrees/kaedevn-adv
なぜ clone でなく worktree で、なぜ消さないか#
理由はビルドの生成物です。
UE の C++ のビルドは、Intermediate/Build(オブジェクトと生成コード)と Binaries が温まっているかで、差分の数分で済むか、作り直しの数十分になるかが決まります。
どちらも追跡しないファイルなので、worktree を消さない限り残ります。
2026-07-28 に測った値で、開発で効く kaedevnEditor の Development は、3 本とも 2.46 GB で温まっていました。
main はさらに、パッケージ用に約 8 GB を持っています。
worktree を作り直すと、これが全部消えます。
だから規約は、
- 常設の worktree を作り直さない
- git worktree add/remove を勝手に実行しない
- 別のブランチは、既存の worktree で checkout する
- 掃除しても Intermediate と Binaries は残す
です。
シェーダとアセットのキャッシュ(DDC)は、UE 5.8 では PC 全体で共有されるので、worktree を跨いで効きます。
worktree ごとに守るのは、C++ のビルドの生成物だけです。
もう一つ、worktree ごとに別になるものに、照合の焼いたベクトル(vectors.bin)があります。
追跡しない生成物なので、main を取り込んだら、その worktree で焼き直さないと、古いベクトルの上で検査が緑になります(T52 の回の罠)。
同時に動かせるのは 2 本#
上限を決めているのは RAM です(15.2 GB)。
UE のエディタは 1 本で 8〜12 GB を使い、2 本起こすとスワップに落ちます。
CPU は 8 物理コアで余っています。
ビルドも RAM に縛られます。
UE のビルドの道具(UBT)は 1 アクションに 1.5 GB を求め、空きを見て並列の数を決めます。
2026-07-28 の実測で、ブラウザ(Chrome 33 プロセス・3.75 GB)を開いたままだと空き 624 MB で並列 1、閉じると 4.88 GB で並列 3、68 アクションが 30.67 秒で終わりました。
ログのこの行が出たら負けです。
Requested 1.5 GB memory per action, 624.52 MB available:
limiting max parallel actions to 1
なので、
- UE エディタは同時に 1 本
- フルビルドも同時に 1 本
- ビルドの前にブラウザを閉じる
が決まりです。
期待できる速さは、2 倍ではなく体感で 1.4〜1.6 倍、と文書に書いてあります。
本当に並列になる組み合わせは一つだけです。
ADV-CLI と、UE 側のどちらか 1 本。
ADV-CLI は UE のビルドの道具を使わず(cmake と cl で直接)、Live Coding にも縛られず、エディタも起こしません。
UE 側の 2 本(ADV-UE と撮影所)は、エディタとフルビルドを交代で使います。
境界はモジュールで引く#
ぶつかるのは「機能」ではなく「モジュール」です。
7 月の直近 60 コミットのうち 44 件(73%)が、画面を描くモジュール(いまの MomijiUISlate・KaedeVnUI)を触っていました。
機能ごとにブランチを分けると、毎回ここでぶつかります。
だから、そのモジュールの持ち主を 1 本の worktree に固定しました。
ADV の CLI と UE の間の継ぎ目は 1 本です。
核のヘッダ(KaedeAdv/Private/core/*.hpp)だけが、両方でコンパイルされます。
今日数えると 20 ファイル・9,766 行です。
CLI のビルドはこのフォルダを include に直接入れていて、写しを持ちません(T27)。
core は ADV-CLI が決め、ADV-UE が従う、の一方通行です。
並列にする前に塞いだ穴があります。
UE は変数の隠し合い(C4458 など)をエラーにしますが、CLI の既定の警告の水準では通ってしまう。
並列だと ADV-CLI は UE をビルドしないので、「CLI は緑なのに UE が落ちる」を自分で見つけられません。
2026-09-11 に CLI の側へ /we4456 /we4457 /we4458 /we4459 を入れ、継ぎ目を CLI だけで検査できるようにしました。
main は合流とビルドだけ#
main の worktree では、機能を作りません。
するのは、レーンの合流、push、パッケージ、文書と、合流の後の UE のビルドとテスト、そこで落ちたものの修正までです。
修正は main でコミットし、レーンへ流します。
合流は「レーン同期して」の一言で、main のセッションが Scripts/Sync-Lanes.ps1 を回します(2026-09-16 に入れた道具)。
順番は 3 段で、スクリプトの契約です。
- レーン → main。main で git merge --no-edit <レーン>。早送りできれば早送り、できなければ合流のコミット。衝突したら --abort して、そのレーンだけ FAIL
- main → レーン。各 worktree で git merge --ff-only main。1 の後は main がレーンを含むので、必ず早送りになる
- 取り込んだ差分から、レーンで次に要る作業を出す。ADV のデータや核や adv-sim が動いていれば next=bake
レーンの worktree が汚れていれば、そのレーンは SKIP です。
レーンの未コミットはレーンのセッションの仕事で、main からは触りません(コミットも stash もしない)。
合否は最後の行だけで読みます。
Result: PASS 3 レーンすべて <合流後の commit>
push はしません。合流はローカルの merge だけで、GitHub へ送るのはユーザーが「push して」と言ったときです。
main のマージコミットを数えると、59 本でした。
7 月 12・8 月 32・9 月 5・10 月 10。
9 月から少ないのは、早送りで入る朝が多いからだと見ています(早送りはマージコミットを作りません。朝ごとの記録は数えていません)。
セッションは自分の worktree から出ない#
セッションは自分の worktree から出ない、というのがいちばん大事な決まりです。
2026-07-28 に、あるセッションが 3 つの worktree を横断して「両方のブランチへ main を取り込む」を実行しました。
ほかのセッションから見ると、自分のブランチが勝手に動いたことになります。
同時に 2 つのセッションが動いていたら、事故になっていました。
いまは、ほかのレーンへの取り込み・状態の確認・編集は全部、統合(main)のセッションの仕事です。
レーンのセッションは、自分のフォルダだけを見ます。
無人で Issue を進めさせる仕組み(Scripts/issue-run.ps1)も、worktree ごとに 1 本です。
起動するときに作業場所の worktree を選び、同じ worktree で二つ起動すると、後の方が止まります。
名前どおりでない所 — git worktree#
git worktree の使い方そのものは、名前どおりです。
共有の .git に、作業ツリーをフォルダごとに持たせ、コミットを push なしで渡しています。
違うのは、使い方の時間の長さです。
ふつうの使い方は、急ぎの修正やレビューのために出して、済んだら消す、です。
うちの 4 本は常設で、作り直しません。
フォルダを残すこと自体が目的(温まったビルドの生成物)になっています。
trunk-based development とも、似ていて違います。
「長いブランチを作らない。差分は 1〜3 コミットで main へ」という規約は、短命なブランチの考え方です。
けれど、ブランチ自体は 3 本とも何か月も同じ名前で続いていて、毎朝 main と揃え直しています。
短命なのはブランチではなく、main との差の方です。
今日は、その規約から外れています。
この原稿を書いている ADV-CLI の worktree は、main より 42 コミット先にあります(全部今日の分で、ほとんどがこの連載の原稿)。
ほかの 2 本は main と同じ位置です。
文書のずれも 2 つありました。
worktree の文書は「3 つの worktree は同一の .git を共有」と書いていますが、今は 4 本です(統合用を入れて)。
継ぎ目の核は「11 ファイル 5,401 行」とありますが、今日は 20 ファイル・9,766 行です。
弱点と、採らなかった案 — git worktree#
一つ目、共有されるものは、全部のフォルダに効きます。
.git/config の pack の設定(スレッド 4・窓のメモリ 256 MB・大きいファイルの閾値 32 MB)を外すと、git gc が OOM で死にます(2026-07-23 に実際に死にました)。
stash の山も共有なので、ほかのセッションの stash を pop してしまう危険があります。
二つ目、追跡しないものは、全部のフォルダで別です。
ビルドの生成物は利点ですが、焼いたベクトルは罠です。
合流の道具は next=bake を出すだけで、焼くのはレーンのセッションです。
出たのに焼かなければ、そのレーンは古いベクトルの上で動きます。
三つ目、一つの worktree を二つのセッションで使うと、インデックスを取り合います。
2026-10-21 に、別のセッションがステージしたファイルが、自分のコミットに混ざりました。
インデックスは worktree ごとに 1 つなので、同じフォルダの中では分けられません。
いまは自分のパスだけを git commit --only -- <パス> でコミットし、コミットの後に中身を確かめています。
四つ目、UE の実行時の仕組みが、worktree の境界をまたぎます。
Live Coding はエンジン全体で 1 つで、別の worktree でゲームが動いているだけで、ほかのレーンのビルドが止まります。
起動に -NoLiveCoding を付けても、起動そのものが Live Coding のコンソールを生むことがありました(2026-08-13)。
ビルドの前には、毎回そのプロセスがいないかを見ます。
ビルドの成否も、終了コードではなくログの Result: の行で読みます。失敗でも 0 を返すことがあったからです。
採らなかった案。
clone を 4 つ。
フォルダの独立は強くなりますが、コミットの受け渡しに push と fetch が要り、オブジェクトも 4 倍になります。
worktree なら、main で git merge <レーン> を実行するだけで、ネットワークに一度も触らずに合流できます。
機能ごとに worktree を作って、終わったら消す。
git worktree のふつうの使い方です。
けれど、作るたびに UE のフルビルドが要ります。
フォルダは作業の種類(CLI・UE の画面・撮影所)で固定し、別の作業はそのフォルダで checkout する、にしました。
main でも開発する。
一人で作っているので、一見いちばん速そうです。
けれど main で機能を作ると、レーンとの差がどちら向きか分からなくなり、合流の道具の「1 の後は必ず早送り」が崩れます。
main は合流と、合流の後のビルドで落ちたものの修正だけ、と決めました。
検索語#
git worktree add linked working tree
git worktree build artifacts per worktree
git merge --ff-only
trunk-based development short-lived branches
4 本の worktree で作った『霧走町 流氷に眠る』の第一章を、ブラウザ版で入力して遊べます。無料で、プロフィールのリンクから。