プログラミング
セットアップスクリプトはGit Worktreesをサポートすべき
Setup Script Should Support Git Worktrees (piechowski.io)
要約
この記事は、開発者が複数のGit worktreeを使用する際に発生する環境設定の課題について論じています。特に、複数のworktreeが同じポート、データベース、キャッシュキーなどを共有しようとすると競合が発生する問題を取り上げ、これを解決するために、各worktreeに一意のIDを割り当て、ポートやリソース名を動的に管理するセットアップスクリプトの重要性を強調しています。
全文翻訳
私が取り組んでいるある本番アプリケーションでは、新しいGit worktreeは1つのセットアップスクリプトで実行可能になります: bin/setup。
セットアップは環境を準備して終了し、その後通常の開発コマンドが実行されます。ここでは、リポジトリがすでに使用しているコマンドのショートハンドとしてbin/devを使用します。
セットアップは、サーバーを巧妙なプロセスマネージャーの中に隠しません。bin/setupは、チェックアウトを動作する開発環境に変換するリポジトリ所有の実行可能ファイルです。プロジェクトに合わせて、make setupやscript/bootstrapと呼んでも構いません。
インターフェースは名前よりも重要です: 1つのコマンドは、準備完了のリポジトリか、正確な修復手順のいずれかを返します。同じコマンドが、すべてのGit worktreeから機能するはずです。
並列コーディングエージェントは、これを通常の開発インフラストラクチャとして扱うことを重要にしました。
2番目のチェックアウトは2番目の環境ではない
Git worktreeは、リポジトリの共通Gitデータを共有しながら、独自のワーキングツリー、HEAD、インデックスを持つ別のチェックアウトです。
その分離はコードにとって素晴らしいです。Gitは、アプリケーションの2つのコピーが同じポート、データベース、キャッシュキー、コールバックURL、Terraform状態、またはコンテナ名を要求していることを知りません。
これは、2つのworktreeが実行する必要があるまで機能します。
git worktree add ../app-payment-fix -b payment-fix
cd ../app-payment-fix
bin/setup
bin/dev
1つの永続的なクローン用に書かれたセットアップスクリプトは、同じ.envをコピーし、すべてのチェックアウトを同じデータベースに向けることができます。
2つのworktreeが開始すると、それらの開発サーバーはホストポートを奪い合う可能性があります。
Composeプロジェクトも、プロジェクト名や固定コンテナ、ネットワーク、またはボリューム名を再利用する場合に重複する可能性があり、公開ポートは依然として衝突します。
2番目のworktreeは、開始に失敗するか、静かに状態を共有します。
Worktreeはコーディングエージェントよりずっと前から存在していました。変わったのは頻度です。
かつては、2つのブランチを開いておくことは時折の便利なことでした。今では、複数のエージェントが同時に無関係な変更を実装およびテストできます。
それらのファイルはGitによって分離されています。それらの実行時状態は、リポジトリによって分離される必要があります。
サービスを共有し、名前を分離する
各worktreeに完全なDockerスタックを実行することは、最も単純な精神モデルであり、しばしば間違ったデフォルトです。
データベース、オブジェクトストレージエミュレーター、メールキャッチャー、その他のローカルサービスは、アプリプロセスと比較して高価になる可能性があります。
私は1つの共有スタックを実行し、各worktreeに独自のポート、データベース、およびリソース名を与えます。
マシンごとに共有
データベースおよびキャッシュサーバー
ローカルサービスコンテナ
パッケージダウンロードおよびツールキャッシュ
ワークツリーごとに分離
開発およびテストデータベース名、キャッシュ名前空間
キュー名、バケット、コールバックURL、インフラリソース名
アプリポート、環境ファイル、ログ、PID、ローカル状態
セットアップスクリプトは、読みやすいworktree IDを導き出し、それを小さなローカルレジストリと照合してから、次のような生成された構成を書き込みます。
WORKTREE_ID=<derived-worktree-id>
APP_PORT=<allocated-port>
DATABASE_NAME=app_dev_<worktree-id>
TEST_DATABASE_NAME=app_test_<worktree-id>
レジストリの割り当てはロックを必要とします。エージェントが同時にセットアップを開始できるためです。
ロックは、それを尊重するセットアッププロセスのみをシリアル化します。
堅牢なアロケーターは、古い予約を削除し、候補ポートを選択し、現在のリスナーをチェックしてから、IDをアトミックにコミットする必要があります。
そのリスナーチェックはスナップショットにすぎません。開発コマンドは、後でバインド衝突を明確に報告する必要があります。
スクリプトは再実行可能ですが、同時実行下では依然として失敗する可能性があります。冪等性だけでは十分ではありません。
bin/setupが所有するもの
セットアップは、git worktree addから通常の開発コマンドまでのすべてのステップを所有します。
リポジトリで宣言されたツールチェーン(miseを使用)をブートストラップします。
bin/doctorを実行して、残りのホスト要件を診断し、必要に応じて正確な修復手順で停止します。
共有Dockerサービスが実行中で正常であることを確認します。
worktreeのIDとポートを割り当てるか、回復します。
ローカル環境およびサービス構成を生成します。
分離された開発およびテストデータベースを作成および移行します。
アプリが依存関係に到達できることを確認してから、終了します。
そのブートストラップはmiseである必要はありません。私はmiseを使用して、リポジトリ内の言語ランタイム、パッケージマネージャー、および小さなCLIツールをピン留めします。
セットアップは、不足している構成済みバージョンに対してmise installを実行できます。
Git、Docker、オペレーティングシステムライブラリ、および認証情報が必要になる場合があります。
リポジトリがmiseを使用している場合、ツールのインストールはすべてのシェルでアクティブになりません。
セットアップは、repoコマンドをmise exec -- ...経由で実行するか、明示的にシェルアクティベーションを要求する必要があるため、偶発的なシェル構成が結果を変更することはありません。
これにより、最初のコミットまでの時間が短縮されますが、オンボーディングはその最初のメリットにすぎません。
人間またはエージェントがworktreeを開くたびに、同じパスが実行されます。
セットアップを早期に失敗させる
bin/doctorはセットアップの開始近くに配置されるべきであり、独立して実行されるべきでもあります。
追加する前に、セットアップはffmpegが欠落していることを発見する前にシードステップに到達する可能性がありました。
Doctorは、高価な作業が開始される前にそれをキャッチします。
有用なdoctorは、すべての要件をチェックし、すべての失敗を収集し、ゼロ以外で終了し、人が貼り付けられる修復を出力します。
FAIL container engine: daemon not reachable
Dockerを起動してから、bin/doctorを再度実行してください。
FAIL tools: configured versions are missing
Run: mise install
Doctorは、安定した終了コード、プロンプトなし、ターミナルまたはエージェントトランスクリプトで機能する出力で、全体のリストを一度に報告する必要があります。
共有スタックの限界
共有データベースを再起動すると、すべてのworktreeが中断されます。
互換性のないサービスバージョンを必要とするブランチは、同じスタックを使用できません。
キャッシュキー、メールボックス、キュー、およびオブジェクト名は、セットアップがworktree IDでそれらをプレフィックスしない限り、依然としてクロストークします。
一部の作業では、より強力な分離が必要です。
破壊的な移行作業、サーバー全体をリセットするテスト、またはデータベースエンジンをアップグレードするブランチは、専用のスタックに値する場合があります。
共有スタックは高速なデフォルトであり、ルールではありません。
リセットおよびテイクダウンコマンドには、同じ境界が必要です。
git worktree removeはアプリのクリーンアップを呼び出さないため、リポジトリ所有のbin/teardownはポートを解放し、そのworktreeのデータベースのみをドロップし、その生成された状態のみを削除する必要があります。
通常のbin/resetは、共有ボリュームや他のチェックアウトのリソースを絶対に消去してはなりません。
マシン全体のすべてのリセットを明示的にし、誤って実行するのが困難にします。
受け入れテストは、コピーされた環境ファイルなしで、クリーンなworktreeから開始する必要があります。
セットアップを2回、同時に別のセットアップの横で実行し、両方の開発サーバーを開始し、両方のテストスイートを実行してから、一方のworktreeをもう一方が実行し続ける間にテイクダウンします。
そこで見つかったすべての手動修正は、bin/setupまたはbin/doctorに属します。
コーディングエージェントは、worktreeを作成し、準備し、アプリを実行し、兄弟に触れることなく離れることができるはずです。
リポジトリがその境界を強制できない場合、エージェントを増やすことは、ローカル環境の失敗を速めるだけです。
関連記事
優れたオープンソースメンテナーになる方法
コードを読む前に実行するGitコマンド
エンジニアリングチームが遅い理由(人々ではなく、コードベースのせい)
Vimでタブを閉じる方法
最初の週にレガシーRailsコードベースを監査する方法