Skip to content

feat(backend): merge 元 PR の枝の baseline を default branch へ引き継ぐ - #58

Merged
yupix merged 3 commits into
mainfrom
feat/baseline-inherit-on-merge
Oct 7, 2026
Merged

yupix merged 3 commits into
mainfrom
feat/baseline-inherit-on-merge

Conversation

@sousuke0422

@sousuke0422 sousuke0422 commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

概要

PR の枝で承認した baseline を、merge 後の default branch のビルドへ引き継ぎます。
GitHub App が使えるプロジェクトが対象です。App が無い場合の動作は変わりません。

Closes VRT-23(親: VRT-22)

背景

baseline は枝の名前だけで解決しています(同じ枝の最新 → default branch の最新)。
そのため、PR の枝で承認した baseline は merge で枝の名前が変わると見えなくなります。
merge 後の default branch のビルドは古い baseline と比べられ、同じ変更の承認をもう一度求めていました。

renovate の squash merge で目立ちます。
依存を上げるたびに、PR と merge 後の main で同じ絵を二度承認することになっていました。

変更点

  • default branch のビルドを作るとき、GET /repos/{repo}/commits/{sha}/pulls で merge 元の PR を引きます
  • PR の枝の最新 baseline を、default branch の baseline として写します
  • 写した行は default branch の baseline なので、比較・承認・plan 添付の解決(latest_for)は変えていません

写すのは、PR の枝の baseline が default branch の最新より新しいときだけです。
default branch がその後に承認されていれば巻き戻しません。
写した行の source_build_id は元の承認ビルドを指すので、系譜の表示とビルドの刈り込みはそのまま正しく働きます。

次の場合は何もせず、これまでの解決のままです。
ビルドの作成は失敗させません。

  • GitHub App が未設定、またはプロジェクトがリポジトリに紐付いていない
  • PR の取得に失敗した
  • merge 済みの PR が無い

merge の手法について

API の返す形は、merge commit・squash・rebase のどれでも同じでした(公開リポジトリで確かめました)。
rebase では、default branch に並んだどの写しの commit からでも同じ PR が返ります。
merge 後に PR の枝が消えていても引けることを、task の renovate の PR(squash と通常の merge)で確かめました。

権限

使う API は「Pull requests」の read を求めます。
既存の「Pull requests: Read and write」(PR コメント用)に含まれるので、App の権限は変わりません。

DB

migration はありません。

試験

tests/github_integration.rs に次を足しました。
GitHub API は wiremock で差し替えています。

  • merge commit・squash・rebase のそれぞれで引き継がれる
  • GitHub App が無い場合、API が失敗した場合、merge されていない PR しか無い場合は引き継がない
  • default branch が後で承認されていれば上書きしない

引き継ぐ側の試験は、実装の前に落ちることを確かめてあります。
引き継がない側の試験は、実装の守りを外すと落ちることを確かめてあります。

cargo test --workspace・cargo clippy --workspace --all-targets -- -D warnings・cargo fmt --all -- --check は通っています。

引き継ぐ baseline の選び方

引き継ぐのは、merge 元の PR の枝で、その PR の番号で承認した baseline の最新です。
枝の名前は merge の後に別の PR が使い回せるので、名前だけで選ぶと、古い merge commit のビルド(再実行など)が、同じ名前の別の PR の baseline を写してしまいます。
ビルドの PR 番号はビルド作成時に申告された値(vrt-actions が送る pull_request_number)です。
PR 番号を持たないビルドで承認した baseline は引き継ぎません。

ビルドの branch と commit_sha も申告された値で、その commit が default branch 上にあるかは確かめません。
写せるのは、同じプロジェクトで承認され merge された baseline だけで、default branch の最新より新しいときだけなので、未承認の絵は入りません。

引き継がなかったときの記録

引き継がなかったときは、その訳をログに残します(did not inherit the merged pull request's baseline、reason=…)。
たとえば、PR 番号を送らないビルド(push で作ったビルドなど)だけで承認した枝は引き継がれず、reason="no_baseline_for_pull_request" が出ます。

引き継がない境界

比べるのは baseline の作成時刻で、引き継いだ行には引き継いだ時刻が入ります。
そのため、default branch の baseline が更新された後に merge された PR は引き継ぎません。
例: PR A と PR B を承認し、A を merge(引き継ぐ)してから B を merge すると、B は引き継がれません。

残る課題

git の祖先から baseline を解決する本筋は VRT-24 で扱います。
App の無い場合や GitHub 以外の forge は、この PR では救えません。

VRT-26 では baseline を名前ごとに合成して引き継ぎます。
いまは名前の集合を丸ごと置き換えるため、上の例の B を A の後に写すと A の承認が消えます。
そのため B は引き継がない境界にしています。

Assisted-by: multi-agent-shogun-aki-tweak

baseline の解決は枝の名前でしか探さないため、PR の枝で承認した baseline が merge 後の default branch のビルドから見えず、同じ変更の承認をもう一度求めていた。renovate の squash merge で依存を上げるたびに同じ絵を二度承認する形になっていた。

default branch のビルドを作るときに GET /repos/{repo}/commits/{sha}/pulls で merge 元の PR を引き、PR の枝の最新 baseline を default branch の baseline として写す。写した行は default branch の baseline なので、比較・承認・plan 添付の解決(latest_for)は変えずに引き継いだものを掴む。

- merge commit・squash・rebase のいずれでも API は同じ形で merge 済みの PR を返す(実 API で確かめた。rebase は前の方の写しでも同じ PR が返り、merge 後に PR の枝が消えていても引ける)
- 写すのは PR の枝の baseline が default branch の最新より新しいときだけ。default branch がその後に承認されていれば巻き戻さない
- source_build_id は元の承認ビルドを指したまま写すので、ビルドの刈り込みが写しの実体も守る
- GitHub App が無い・紐付いていない・API が失敗した・merge 済みの PR が無いときは何もせず、いまの二段解決のまま。ビルドの作成は失敗させない
- 必要な権限は既存の Pull requests(read を含む)で足りる

この変更は VRT-23 に当たる。git の祖先から解決する本筋は VRT-24 で扱う。

Assisted-by: multi-agent-shogun-aki-tweak
引き継ぐ baseline を枝の名前だけで選んでいた。枝の名前は merge の後に別の PR が使い回せるので、merge 済みの PR の枝名を新しい未 merge の PR が使い、その baseline を承認したあとで古い merge commit のビルドを作る(再実行など)と、別の PR の baseline が default branch へ写っていた。

写す元を、承認したビルドの PR 番号が GitHub から得た merge 元 PR の番号と一致する baseline の最新に限る。ビルドの PR 番号はビルド作成の要求が申告する値で、vrt-actions は github.event.pull_request.number を送る。PR 番号を持たないビルドで承認した baseline は写さない。

あわせて doc に二つを書いた。
- 何を信頼しているか。branch と commit_sha は申告で、sha が default branch 上に在ることも merge_commit_sha の一致も確かめない(後者は rebase merge のための意図した緩さ)。写せるのは同じプロジェクトで承認され merge された組だけで、default branch の最新より新しいときだけなので、祖先を確かめる API は足さない
- created_at の境界。比べるのは行の created_at で、写した行には写した時刻が入るので、default branch の baseline が更新された後に merge された PR は引き継がない。source.created_at を保つだけでは、写しが名前の集合を丸ごと置き換えるので先の承認が消える。名前ごとの合成は VRT-26 で扱う

試験を三本足した。枝名を使い回した形、同じ merge commit の二度目のビルドで baseline の行が増えないこと、PR A の写しの後に merge した PR B を引き継がないこと。

Assisted-by: multi-agent-shogun-aki-tweak
inherit_from_branch は写さなかったときにどの訳でも None を返し、呼び手は何も残していなかった。PR 番号を申告しないビルド(push で作ったビルドなど)だけで承認した枝では引き継ぎが発火しないが、その訳がどこにも出なかった。

戻り値を Inheritance(Inherited / SourceIsDefaultBranch / NoBaselineForPullRequest / AlreadyInherited / DefaultBranchIsNewer)にし、inherit_merged_pr_baseline は写さなかったときに訳を info で残す。inherit_from_branch の呼び手はここだけである。

already_inherited の doc を実態に合わせた。ふつうは created_at の比べでも止まるが、写しと元の時刻は別のプロセスの時計で入るので、写した側の時計が遅れていれば写しの方が古くなりうる。そのとき二度写しを止めるのはこの守りだけである。

試験を二本足した。push のビルドだけで承認した枝を引き継がず NoBaselineForPullRequest を返すこと。写しの行の created_at を元より古く書き換えた形で、already_inherited だけが二度写しを止めること。

Assisted-by: multi-agent-shogun-aki-tweak
@yupix
yupix merged commit cf39c6d into main Oct 7, 2026
5 checks passed
@yupix
yupix deleted the feat/baseline-inherit-on-merge branch October 7, 2026 22:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants