129. Train naskládaných pull requestů přistane v jednom běhu povinné kontroly, každý článek na verdiktu, který jeho hlava už má
Kontext
ADR 0101 učinilo integraci jedním merge naráz a pojmenovalo cenu: „jeden pull request přistane za jeden běh CI jeho obnoveného headu“, přičemž „paralelní spekulativní obnovy — merge queue“ nechalo jako „pozdější rozhodnutí“, které nejprve potřebovalo důkazy. Toto je to rozhodnutí a toto jsou ty důkazy, změřené dne 2026-10-10.
- Běh
validatepull requestu stojí 398 minut úloh (run 38054235798) a push na master 521 (run 38043095416). Reálný čas je 85 až 100 minut a určuje ho nejpomalejší shard sady: 83 minut pro shard 1 vedle 41 pro shard 3 ve stejném běhu. - Stopa executoru v primárním checkoutu drží 15 ověřených merge za čtyři dny: 3, 7, 2 a 3 denně. Bylo otevřeno padesát dva pull requestů a 23 z nich bylo
needs_refresh: hotových, čistých s odvozeným ovladačem a pozadu za masterem jen proto, že přistálo něco jiného. - Každý merge posune odvozené artefakty, které nese každá jiná větev (ADR 0101), takže head obnovený na master je znovu pozadu, jakmile přistane jakýkoli jiný pull request. Obnova dvou najednou plýtvá během toho druhého. Z tohoto důvodu je potrubí jedno hluboké, z žádného jiného.
2026-10-10 vlastník schválil naskládaný vlak a jedna relace jej od té doby provozuje ručně: každý head se aktualizuje na připravený head předchozího, ne na master. Když nižší přistane, sloučení masteru do vyššího nezmění žádný soubor. To bylo ověřeno na prvním skutečném článku, kde sloučení masteru po #880 do headu připraveného pro #881 zapsalo vlastní strom onoho headu. Zeptáno se gitu na celou dráhu téhož odpoledne: každému z jedenácti připravených headů (refs/prep/*) chyběly tři commity masteru 815f444f77, všechny tři merge commity, a sloučení každého zapsalo jeho vlastní strom.
Fronta to nedokázala říct. Její vztah k masteru znal pro čistý merge dvě odpovědi: head obsahuje master (up_to_date), nebo ne (behind). Horní článek merge commit masteru neobsahuje, takže byl behind, jeho další akcí byla obnova a obnova je tam merge commit, který nic nemění, nový head a druhý běh povinné kontroly pro strom, který první běh už posoudil. Alternativou linky bylo pokaždé se zeptat vlastníka, než se kontrola čerstvosti projde ručně, a ručně projitá kontrola není kontrola.
Ani vyžádaný běh není cesta, jak to obejít. Dne 2026-10-10 skončil běh workflow_dispatch workflow validate úspěchem na hlavě pull requestu #866, s kontrolním během ci od aplikace, kterou ochrana větve vyžaduje, a forge přesto odmítl merge (úkol I2282). Verdikt tedy musí být ten, který vytvořila vlastní událost pull requestu, na hlavě, která se mergeuje.
Rozhodnutí
-
Třetí odpověď pro čistý merge.
RelationToMaster::CarriesMasterje head, který neobsahuje master, ačkoli master nedrží nic, co mu chybí. Obě poloviny rozhoduje git v databázi objektů. Merge zapíše vlastní strom headu:git merge-tree --write-tree master headje čistý a vytiskne strom, kterýhead^{tree}pojmenovává. A každý commit, který master má a head nemá, je merge commit:git rev-list --count --no-merges head..masterje nula. Nic jiného to nezpůsobuje. Ne stackování, ne deklarace, ne záznam o tom, jak byl head sestaven. -
Proč obojí a proč to stačí. Pokud merge zapíše strom headu, master od společného základu nezměnil žádnou cestu, kterou by head už nedržel ve stejném stavu. Takže se nesloučí žádná cesta: žádný driver nic nerozhoduje, vlastní merge forge zapíše týž strom bez odvozeného driveru a strom, který posoudily povinné kontroly, je strom, kterým se master stane. Strom není vše, co posoudily. Odvozený výstup se skládá i z historie: changelog vypisuje každý commit, který není merge (
git log --no-merges,release/commits.rs). Master, který přijal změnu a vrátil ji, má strom, jaký měl, a dva commity, které žádný changelog na headu nikdy nevypsal, takže by první polovina sama přistála se zastaralým changelogem. Sada to řekla dřív než jakýkoli pull request: první návrh tohoto vztahu se ptal jen na strom a dva existující testy, vrácený cherry-pick a prázdný commit na masteru, vyšlycarries_master. Když chybějí jen merge commity, každý commit, který generátor vypisuje, je takový, který head má. Tato dvojice je vlastnost, pro kterou se věřiloup_to_date. Obsahovat master je jeden způsob, jak ji mít, ne jediný. -
Kontrola aktuálnosti projde u obou. Hlava
carries_masters prošlými povinnými kontrolami jereadya jejím prvním důvodem jecarries_master, kde aktuální říkácontains_master, takže stopa oba případy rozliší.prs repairna ní nemá co opravovat. Dokud její kontrola čeká na hlavě, kterou executor odeslal, drží pipeline obnovy úplně stejně jako aktuální hlava. -
Vlak jsou naskládané hlavy a deklarované pořadí. Článek k+1 se aktualizuje na obnovenou hlavu článku k a ve svém těle říká
Stacked on #k. Kontrola závislostí už tento řádek čte (ADR 0101), takže článek jewaiting_for_dependency, dokud neproběhl merge toho pod ním, ať říkají kontroly cokoli. Pořadí je kontrola. Kontroly všech článků běží najednou, každá na hlavě, která drží vše pod sebou, a články přistávají jeden po druhém bez obnovy a bez druhého běhu. -
Červený článek stojí to, co nestojí žádný vlak. Články nad ním čekají na svou závislost. Když je opraven a přistane, master drží změnu, kterou jejich heady nikdy neviděly, jsou
behinda každý si vezme jedno běžné obnovení. Článek uzavřený bez merge nechává články nad seboublockedsdependency_closed_unmerged, pro člověka. V žádném případě se nesloučí strom, který žádný běh neposoudil. -
Cachovaný vztah patří slovníku, který o něm rozhodl. Cache vztahů držela položku navždy pod dvojicí id commitů.
behind, napsané před existencícarries_master, může být kterýkoli, takže cache nyní zaznamenává, který slovník rozhodl o jejích položkách, a vztahy jiného zahazuje. Tvary vedle nich zůstávají: žádný vztah nerozhoduje o tvaru. -
Executor zatím vlak nesestavuje. O každém článku rozhoduje podle toho, co říká git, ať ho naskládal kdokoli.
prs drain --refreshstále obnoví jeden pull request na master a čeká. Obnovení dalšího kandidáta na head horního článku do hloubky deklarované v.ai/repo/ci/queue.yaml, se zaznamenaným stackováním ve stopě a deklarací zapsanou executorem, je samostatný úkol, který toto rozhodnutí umožňuje a neobsahuje.
Důsledky
Vlak o n článcích přistane zhruba za jeden běh povinné kontroly místo n, nakolik runnery dovolí: běh každého článku je 398 minut úlohy, ať běží postupně nebo společně, takže vlak zkracuje reálný čas a neutrácí méně minut. Push na master, který každý merge spustí, stále běží celý, 521 minut úlohy pro strom, který běh pull requestu právě posoudil. Zda smí tento běh spočívat na stejném důkazu, se zde nerozhoduje.
Pull request odkazu ukazuje ve svém diffu odkazy pod sebou, dokud nepřistanou, a recenzent odkazu k+1 čte i odkaz k. Stacked on #k říká proč.
Horní odkaz, jehož tělo nejmenuje ten pod ním, gate závislostí nedrží. Pokud jeho head obsahuje i master, je dnes up_to_date, jako před tímto rozhodnutím, a mohl by přistát první s commity nižšího uvnitř: odvozená závislost je důkaz a nikdy blokace (ADR 0101). Deklarace je tedy součástí stackování headu a úkol, který exekutoru umožní postavit train, ho nechá řádek napsat.
První pozorování po upgradu se jednou znovu zeptá gitu na každou relaci aktuálního masteru.
Jedna věc je zde argumentována a dosud neměřena. Generátor, který by vypisoval merge commity, by způsobil, že odvozený výstup nesené hlavy se liší od masterova. Žádný takový není znám: changelog je vylučuje a vlastní běh masteru projde generate --check na merge commitu, který odvození žádného pull requestu nikdy neviděla, což by generátor vypisující merge nepřežil. Měřením je první článek, který přistane nesený: běh, který jeho merge spustí na masteru, musí projít generate --check a kontrolami structure. Pokud ne, vztah je chybný a stahuje se, za cenu obnovy, kterou ušetřil.
Zamítnuté alternativy
- Ručně projít kontrolou aktuálnosti pro známý vlak. Slovo člověka o vlastnosti, kterou git rozhodne za milisekundy, požadované znovu pro každý článek a chybějící ve stopě.
- Mergovat master do článku poté, co přistane ten nižší. To je dnešní refresh: nezmění žádný soubor, vytvoří nový head a koupí druhý běh o 85 až 100 minutách.
- Posoudit spekulativní ref dispečovaným během a přesunout head pull requestu na něj. Forge nevzal dispečované
cijako verdikt pro #866 (I2282) a push, který přesouvá head, spustí vlastní běh pull requestu v každém případě. - Merge queue forge. Její mergy nemohou spustit odvozený driver o nic víc než její příznak
mergeable(ADR 0101), takže by její spekulativní mergy kolidovaly na týchž odvozených souborech. Zde nevyzkoušeno; zamítnuto na základě této úvahy. - Přestat commitovat odvozený výstup v pull requestech. Odstranilo by to konflikt, který způsobuje každý merge, a mění to, čím commit tohoto repozitáře je. Větší rozhodnutí než vztah a ne toto.
- Důvěřovat stackování. „Bylo postaveno na #k, takže je bezpečné po #k“ platí, dokud se nepohne některý head. Vztah se ptá gitu na commity, které tam jsou.