Merge-Holds
Ein Merge-Hold stoppt Merges in einen Branch, aus einem bestimmten Grund, bis zu einem bestimmten Zeitpunkt. Setze einen für den Schnitt eines Release-Trains, einen Vorfall oder eine Migration, die nicht mit anderen Änderungen kollidieren darf. Release-Trains setzen und lösen ihre eigenen Holds (source: train), deren Owner der Owner des Trains ist.
Ein Hold gilt für eines davon:
- jedes Repository der Organisation;
- eine Liste von Repositorys (bis zu 50);
- ein Repository.
Er gilt immer für einen Branch, main, sofern du keinen anderen nennst.
Wie ein Hold einen Merge stoppt
fremforge setzt einen Commit-Status, fremforge/hold, auf den Head-Commit jedes offenen Pull Requests in einen gehaltenen Branch:
| Status | Bedeutung |
|---|---|
pending | Ein Hold gilt für diesen Pull Request. Die Beschreibung nennt den Owner, das Ablaufdatum und den Grund. |
success | Kein Hold gilt für ihn, oder ein Admin hat diesen Pull Request durchgelassen (siehe Admin-Übersteuerung). |
Auf einem Branch, auf dem fremforge/hold ein erforderlicher Check ist, stoppt ein pending-Hold jeden Weg zum Merge:
- Der Merge-Button und der Merge-Endpunkt der API lehnen mit „Not all required status checks successful“ ab.
- Auto-Merge wartet. Wird der Hold gelöst, wechselt der Status auf
success, und jeder Pull Request, für den ein Merge vorgemerkt ist, wird dann gemergt. Ein Pull Request, der während eines Freeze vorgemerkt wird, landet danach. - Die Merge-Warteschlange hält den Pull Request aus einer Landegruppe heraus. Dauert der Hold länger als das Check-Timeout der Warteschlange, stellt die Warteschlange den Pull Request zurück und nimmt ihn einmal wieder auf, sobald seine Checks wieder grün sind.
Ein Push in einen Pull Request unter einem Hold oder das Öffnen eines neuen bekommt den Status innerhalb von Sekunden. Ein Abgleichsjob repariert alle 5 Minuten jeden Status, der fehlt, veraltet ist oder von jemand anderem gesetzt wurde.
Holds schlagen sicher fehl. Kann fremforge den Status nicht setzen, wartet ein neuer Pull Request in einen Branch mit erzwungenen Holds, bis es das kann.
In der Konsole
Owner der Organisation verwalten Holds unter Build und Deployment → Merge-Holds (https://frem.sh/<org>/_admin/merge-holds): die aktiven Holds mit ihrem Owner, ihrem Ablaufdatum und dem Ort, an dem der Merge-Button wartet, Buttons zum Lösen, Verlängern oder Durchlassen eines einzelnen Pull Requests, ein Formular zum Setzen eines Holds, die Schalter für die Durchsetzung und die kürzlich beendeten Holds. Alles auf der Seite gibt es auch in der API unten.
Durchsetzung einschalten
fremforge/hold als erforderlichen Check zu verlangen ist optional, pro Repository oder für die ganze Organisation, pro Branch. Bis du dich dafür entscheidest, werden Holds aufgelistet und von Tools gelesen, aber der Merge-Button wartet nicht auf sie.
curl -X PUT https://frem.sh/_app/api/v1/orgs/<org>/merge-holds/enforcement \
-H "Authorization: Bearer $FREMFORGE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"repo": "*", "branch": "main"}'repo ist ein Repository-Name oder * für jedes Repository, auch später angelegte. Der Token braucht policy:write.
Wenn du die Durchsetzung einschaltest, setzt fremforge zuerst fremforge/hold auf jeden offenen Pull Request in den Branch. Erst dann fügt es den Check zur Schutzregel des Branches hinzu. Kein Pull Request wartet jemals auf einen Check, den niemand gesetzt hat.
Die Antwort listet auf:
required_on: Repositorys, in denen der Check jetzt erforderlich ist.unprotected: Repositorys, deren Branch keine Schutzregel hat. fremforge legt keine an; füge eine Regel hinzu, und der Abgleichsjob macht den Check innerhalb von 5 Minuten erforderlich.failed: Repositorys, deren Pull Requests nicht gelesen werden konnten. Ihre Regel bleibt unverändert, und der Job versucht es erneut.
Zum Ausschalten sendest du denselben Body mit DELETE. fremforge entfernt den Check überall dort, wo keine andere Aktivierung das Repository noch abdeckt.
Einen Hold setzen
curl -X POST https://frem.sh/_app/api/v1/orgs/<org>/merge-holds \
-H "Authorization: Bearer $FREMFORGE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"repos": ["platform", "infrastructure-azure"],
"reason": "Train 42 cut: no merges until the tst probe passes",
"duration_minutes": 120
}'| Feld | Pflicht | Hinweise |
|---|---|---|
reason | ja | Eine Zeile, bis zu 500 Zeichen. Wird im Status jedes gehaltenen Pull Requests angezeigt. |
expires_at oder duration_minutes | genau eines | Mindestens 5 Minuten und höchstens 7 Tage in der Zukunft. expires_at ist ISO 8601 mit Zeitzone. |
repos | nein | Weglassen, um jedes Repository zu halten. |
branch | nein | Ein Branch-Name, ohne Platzhalter. Standard main. |
owner | nur für einen Token, der für keine Person handelt | Ein Mitglied der Organisation. |
Der Token braucht deployments:write, denselben Scope, mit dem auch Deployments pausiert werden. Zum Lesen von Holds braucht er deployments:read.
Owner
Jeder Hold gehört einer Person. Wenn du, oder ein Agent in deinem Auftrag, einen Hold setzt, bist du sein Owner. Ein Token, der für keine Person handelt, etwa ein CI-Token, muss owner angeben.
Ablauf und Alarm
Kein Hold blockiert für immer. Erreicht ein Hold sein Ablaufdatum, löst fremforge ihn innerhalb von 5 Minuten, erfasst merge_hold.expired im Audit-Log und schickt dem Owner eine E-Mail. Müssen Merges weiter gehalten werden, verlängere ihn vorher:
curl -X PATCH https://frem.sh/_app/api/v1/orgs/<org>/merge-holds/<id> \
-H "Authorization: Bearer $FREMFORGE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"duration_minutes": 60}'Holds auflisten und lösen
# Live holds, and where enforcement is on
curl https://frem.sh/_app/api/v1/orgs/<org>/merge-holds \
-H "Authorization: Bearer $FREMFORGE_TOKEN"
# Release one
curl -X POST https://frem.sh/_app/api/v1/orgs/<org>/merge-holds/<id>/release \
-H "Authorization: Bearer $FREMFORGE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"note": "Train 42 is out"}'Hänge ?state=all an die Liste an, um auch gelöste Holds einzuschließen. Gehaltene Pull Requests wechseln innerhalb von Sekunden nach dem Lösen auf success.
Das Setzen und Lösen eines Holds erscheint auch im Event-Feed als hold.placed und hold.released, ein Ereignis pro Repository, für das der Hold gilt.
KI-Assistenten, die über den MCP-Server von fremforge verbunden sind, können Holds mit dem Tool list_merge_holds auflisten.
Admin-Übersteuerung
Es gibt zwei Übersteuerungen, und beide werden im Audit-Log erfasst:
- Den Hold eines anderen lösen. Jeder Owner der Organisation kann jeden Hold lösen. Löst ihn sein Owner, erfasst das Audit-Log
merge_hold.released. Löst ihn jemand anderes, erfasst esmerge_hold.override. - Einen Pull Request durchlassen. Für einen Hotfix während eines Freeze nimmst du einen einzelnen Pull Request aus. Sein Status wechselt auf
success, und der Hold bleibt für alles andere bestehen. Ein Grund ist Pflicht. Das Audit-Log erfasstmerge_hold.exempted.
curl -X POST https://frem.sh/_app/api/v1/orgs/<org>/merge-holds/<id>/exemptions \
-H "Authorization: Bearer $FREMFORGE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"repo": "platform", "pr_number": 812, "reason": "INC-7 hotfix"}'Ein Admin eines Repositorys kann weiterhin von Hand mergen, wo die Branch-Regel nicht für Admins gilt. Das ist der Break-Glass-Weg, und Forgejo erfasst ihn wie jeden Merge.
Release-Freezes über ein Issue
Wenn dein Team ein Repository einfriert, indem es ein Issue mit dem Label release-freeze öffnet, funktioniert das weiterhin. In einem Repository mit eingeschalteter Durchsetzung macht fremforge aus jedem offenen release-freeze-Issue einen Hold auf diesem Repository:
- der Autor des Issues ist sein Owner;
- sein Grund ist der Titel des Issues;
- das Schließen des Issues oder das Entfernen des Labels löst ihn innerhalb von Sekunden.
Damit respektiert jetzt auch der Merge-Button den Freeze. Ein Freeze hat kein eigenes Ende, deshalb läuft sein Hold jeweils 24 Stunden: Solange das Issue offen ist, erneuert fremforge den Hold, wenn er abläuft, und schickt dem Owner eine E-Mail, dass der Freeze noch gilt.
Was ein Hold nicht tut
- Er stoppt keine direkten Pushes in den Branch. Verwende Branch-Schutz, um Pull Requests zu verlangen.
- Er stoppt keine Deployments. Pausiere dafür eine Deployment-Umgebung.
- Jeder mit Schreibzugriff auf ein Repository kann einen Commit-Status unter beliebigem Namen setzen. fremforge setzt
fremforge/holdzurück und erfasstmerge_hold.status_foreignim Audit-Log, wenn jemand anderes ihn setzt. Das schließt die Lücke innerhalb von Sekunden bis Minuten, nicht sofort.
Audit-Ereignisse
| Ereignis | Wann |
|---|---|
merge_hold.created | Ein Hold wurde gesetzt, auch ein aus einem release-freeze-Issue übernommener Hold. |
merge_hold.updated | Sein Owner hat ihn verlängert oder den Grund geändert. |
merge_hold.released | Sein Owner hat ihn gelöst, oder sein release-freeze-Issue wurde geschlossen. |
merge_hold.override | Jemand anderes als der Owner hat ihn gelöst oder geändert. |
merge_hold.exempted | Ein Pull Request wurde durchgelassen. |
merge_hold.expired | Er hat sein Ablaufdatum erreicht und wurde gelöst. |
merge_hold.renewed | Ein release-freeze-Hold ist abgelaufen, während sein Issue noch offen war. |
merge_hold.enforcement_enabled / merge_hold.enforcement_disabled | Die Durchsetzung wurde ein- oder ausgeschaltet. |
merge_hold.status_foreign | Jemand anderes hat fremforge/hold gesetzt, und fremforge hat ihn zurückgesetzt. |