同じtreeで通った門は流し直さない、全件の検査を1変更3回から1回に
手元の検査一式を、PR の前、merge の後、リリースの中で同じ commit に重ねて流していた。tree の hash が同じで全部通った結果があれば使い回すように直し、1変更あたりの全件は3回から1回になった。
記事の内容を自社でどう進めるか、内容が固まっていない段階でも相談できます。仕事の相談
毎週 1 回、その週の記事から 5 本と実践の 1 本を選んでメールで届けます。ニュースレターを購読する(無料)

SAGUSの自社ツールのリポジトリsagus-studioはGitHub Actionsを使わず、手元の検査スクリプトgate.shでinstallからe2e、秘密情報の検出までの全件を流す。社内ではこの検査一式を「門」と呼ぶ。門はPRを出す前、mergeの後、リリース用のship.shの中の3回流れていた。mergeの後とship.shは同じcommitで、同じ全件を2度流していた。同じtreeで全部通った結果があれば使い回すように直し、1変更あたりの全件は3回から1回になった。ただし、PRのheadがmergeの時点のmainを取り込み済みの時に限る。
同じtreeなら結果を使い回す
きっかけは開発全体の見直しだった。記録には、筆者が「改修のたびにCI・E2Eを回すと時間とコストが掛かりすぎる」と指摘したとある。これを受けてCIとテストの重複を減らす規約を直す親のissueが立ち、その案の一つが「同じtreeの全件を2度流さない」だった。sagus-studioへの適用は子のissueとして切り出され、処理役のAIが実装した。
門の中身はinstall、format、lint、typecheck、unit、db、build、e2e、audit、gitleaksの全件で、結果は .loopの下の時刻ごとのフォルダのgate.mdに書かれる。ship.shは183行目でgate.shを呼んでいた。つまりリリースのたびに、mergeの後に通したばかりの全件をもう一度流していた。
直し方は三つの部品でできている。一つ目は、門が全部通った時にtreeのhashを結果に残すことだ。hashはgit rev-parseでHEADのtreeを指して取る。二つ目は、次に門を流す時、同じtreeで全部通った結果があればstepを流さず、「使い回し」と結果のpathを書いて通すことだ。三つ目はship.shとmergeの後の門が、この結果を使うようにすることだ。
使い回してよい条件は絞った。treeのhashが一致し、全stepが通った時だけで、作業ツリーに未commitの変更があれば使わない。未commitの変更はgit statusのporcelain形式 の出力が空かで見る。commitのhashでなくtreeのhashで比べるのは、squash mergeではmainに新しいcommitができるからだ。mainのtreeがPRのheadで通したtreeと同じなら、mergeの後の門は流し直さない。障害の訓練を流す --fault-drillの挙動は変えず、ship.shのPreviewと本番の受入試験、ZAPの検査は別の対象を見るので残した。
1変更あたり3回から1回へ
完了のコメントの数字は、1変更あたりの門の全件が3回から1回になったというものだ。残る2回 (mergeの後とship.sh) はauditとgitleaksだけを流し、1〜3秒で終わる。試験用の一時リポジトリでは0〜1秒だった。
全件1回の所要は、社内の端末A2で82〜107秒だった。もう一台の端末Bでは、失敗を含む回が116秒かかった。直す前は、この全件がmergeの後とship.shでもう2回流れていた。1変更あたりで減った合計の時間は記録に無く、未測定である。
確かめ方もissueに先に書いてあった。同じcommitでgate.shを2回流し、2回目が使い回しで数秒で終わること、1ファイルを変えると全件が流れることを確かめる。使い回しが効く場面と、効いてはいけない場面の両方を試す形だ。
残った条件と端末の回り道
この数字には条件が付く。全件が1回で済むのは、PRのheadがmergeの時点のmainを取り込み済みの時に限る。取り込んでいなければsquash merge後のmainのtreeはPRのheadと変わるので、mergeの後に全件がもう一度流れ、回数は減らない。
同じ日には端末の差で門が落ちる回り道もあった。門のdbのstepはinitdb、pg_ctl、psqlをPATHから使う。A2には手で入れてあったが、端末の構成を宣言するファイル (Brewfile) に無く、Bでdbが「initdbが無い」で落ちた。Bに入れて門が全部通ったので、postgresql@17を宣言に足して、環境を作り直した時や他の端末でも戻るようにした。Bの116秒の回とこの失敗が同じ回かは、記録からは分からない。
筆者が注目したのは、使い回しの条件をcommitの一致でなくtreeの一致にし、全stepの通過と未commitの変更が無いことを重ねた点だ。どれか一つでも欠けると、通っていない状態を通ったと扱う穴になる。
同じ考えの手元の門は、受入試験の別のリポジトリsagus-acceptanceにもある。こちらへの同じ直しは、範囲を広げずに別のissueへ分けた。
門の結果を使い回す手順
手元やCIで同じ全件を何度も流しているなら、次の順で使い回しを入れられる。
- 検査を流す場所を全部書き出し、同じcommitか同じtreeに対して重ねて流している組を見つける。
- 検査が全部通った時だけ、git rev-parseで取ったtreeのhashと時刻を結果のファイルに書く。
- 検査の頭で、git status --porcelainが空で、同じtreeのhashで全部通った結果がある時だけstepを飛ばし、使い回したことと結果のpathをログに書く。
- squash mergeを使うなら、PRのheadにmainを取り込んでからmergeする運用にし、mainのtreeとheadのtreeが一致する場面を増やす。
- 使い回す段でも、sagus-studioのauditとgitleaksのように数秒で終わる検査は残して毎回流す。
- 同じtreeで二度流して二度目が飛ぶこと、ファイルを一つ変えると全件が流れることを確かめる。
筆者の見方
全件を減らす時に効いたのは、検査を削ることでなく、同じものを二度見ない条件を決めることだった。treeの一致、全stepの通過、未commitの変更なしの三つがそろった時だけ飛ばすので、本番に出す前の検査の質は変えていない。次は、1変更あたりで減った時間を測り、受入試験のリポジトリにも同じ直しを入れる。CIの側の重複も同じ考えで減らしていく。この仕組みを自社で2週間で作りたい時は、スプリント0で相談を受けている。
出典
に確認(日本時間)
- git rev-parse(git-scm.com)
- git status の porcelain 形式(git-scm.com)
- squash merge(docs.github.com)
- スプリント0(sagus.io)
この記事は役に立ちましたか
ありがとうございます
ありがとうございます



