Effiziente Pipelines beginnen beim Branching-Modell
Eine langsame CI-Pipeline liegt selten nur an der YAML-Datei. Wie ein Team branched, committet und Pull Requests strukturiert, entscheidet maßgeblich darüber, wie effizient GitHub Actions arbeiten kann.
Inhaltsverzeichnis
- 1. Warum Branching-Modell und CI-Trigger zusammengehören
- 2. Trigger-Strategien im Detail
- 3. Branch Protection und Required Checks als Gate
- 4. Caching-Strategien aus der Git-Historie ableiten
- 5. Path-Filter für Monorepos und selektive Pipelines
- 6. Tags und Releases als eigene Trigger
- 7. Concurrency-Gruppen für parallele Pull Requests
- 8. Commit-Konventionen als Pipeline-Input
- 9. Sicherheit: Secrets und Fork-Pull-Requests
- 10. Zusammenfassung
- 11. FAQ
1. Warum Branching-Modell und CI-Trigger zusammengehören
Ein Workflow, der bei jedem Push auf jedem Branch die komplette Test-Suite ausführt, verschwendet in einem Team mit vielen kurzlebigen Feature-Branches erhebliche Rechenzeit. Die Trigger-Konfiguration sollte deshalb genau zum tatsächlichen Branching-Modell des Teams passen.
Bei trunk-based Development mit kurzen Branches lohnt sich eine schlanke Pipeline pro Push, kombiniert mit einer umfassenderen Prüfung erst beim Pull Request gegen den Hauptzweig. Bei längeren Release-Branches ergibt dagegen eine zusätzliche, seltener laufende Vollprüfung mehr Sinn.
Wer die Pipeline unabhängig vom Branching-Modell entwirft, zahlt entweder mit unnötig langen Wartezeiten für Entwickler oder mit unnötigen Kosten für Compute-Minuten, oft sogar mit beidem gleichzeitig.
# .github/workflows/ci.yml, Ausschnitt: unterschiedliche Trigger für Push und PR
on:
push:
branches: [main]
pull_request:
branches: [main]
2. Trigger-Strategien im Detail
Der push Trigger eignet sich für schnelle Rückmeldung direkt nach dem Commit, sollte aber auf relevante Branches beschränkt werden, um nicht jeden experimentellen Branch vollständig zu prüfen. Der pull_request Trigger ist der richtige Ort für die eigentliche Qualitätssicherung vor einem Merge.
Ein häufig übersehenes Detail: pull_request läuft standardmäßig auf dem Merge-Commit zwischen Branch und Zielbranch, nicht auf dem letzten Commit des Feature-Branches selbst. Das führt manchmal zu Ergebnissen, die von einem lokalen Testlauf abweichen, wenn der Zielbranch inzwischen weitergezogen ist.
Zusätzlich lassen sich Trigger über paths und paths-ignore auf bestimmte Verzeichnisse einschränken, sodass etwa reine Dokumentationsänderungen keine vollständige Test-Pipeline auslösen, was in großen Repositories erheblich Zeit spart.
on:
pull_request:
branches: [main]
paths:
- 'src/app/code/**'
- 'composer.lock'
paths-ignore:
- 'design/**'
- '**.md'
3. Branch Protection und Required Checks als Gate
Ein Workflow allein garantiert noch keine Qualität, solange ein Merge auch bei fehlgeschlagenen Checks möglich bleibt. Erst Required Status Checks in den Branch-Protection-Regeln des Hauptzweigs machen bestandene Pipelines zur Voraussetzung für einen Merge.
Wichtig dabei ist die exakte Benennung: Required Checks beziehen sich auf den Namen des Jobs innerhalb des Workflows, nicht auf den Namen der YAML-Datei. Wird ein Job umbenannt, ohne die Branch-Protection-Regel anzupassen, blockiert GitHub den Merge dauerhaft, weil der erwartete Check nie mehr gemeldet wird.
Für besonders sensible Bereiche lässt sich zusätzlich CODEOWNERS mit Required Reviews kombinieren, sodass ein Merge sowohl technische Checks als auch eine fachliche Freigabe der zuständigen Person voraussetzt.
# Required Status Check muss exakt dem Job-Namen entsprechen
jobs:
phpstan:
name: PHPStan Level 5
runs-on: ubuntu-latest
steps:
- run: bin/analyse app/code/Mironsoft --level=5
4. Caching-Strategien aus der Git-Historie ableiten
Der größte Zeitfresser in vielen Pipelines ist die wiederholte Installation von Abhängigkeiten. Ein Cache-Key, der aus dem Hash der jeweiligen Lockdatei berechnet wird, sorgt dafür, dass der Cache nur dann invalidiert wird, wenn sich die tatsächlichen Abhängigkeiten geändert haben, nicht bei jedem einzelnen Commit.
Da composer.lock und package-lock.json versioniert sind, lässt sich ihr Inhalt zuverlässig über die hashFiles Funktion in einen Cache-Key einbauen. Ändert sich die Lockdatei nicht, greift der Cache treffsicher, unabhängig davon, wie viele andere Dateien im selben Commit geändert wurden.
Ein zusätzlicher Restore-Key mit einem allgemeineren Präfix erlaubt es, zumindest teilweise auf einen älteren Cache zurückzugreifen, selbst wenn der exakte Hash nicht mehr existiert, was die Installationszeit im schlechtesten Fall trotzdem deutlich reduziert.
- uses: actions/cache@v4
with:
path: vendor
key: composer-${{ hashFiles('composer.lock') }}
restore-keys: |
composer-
5. Path-Filter für Monorepos und selektive Pipelines
In Projekten mit mehreren unabhängigen Modulen im selben Repository ist es ineffizient, bei jeder Änderung alle Module zu testen. Path-Filter erlauben es, Jobs nur dann auszuführen, wenn tatsächlich Dateien im relevanten Verzeichnis betroffen sind.
Für komplexere Fälle bietet sich eine dedizierte Action wie dorny/paths-filter an, die als eigener Job den Vergleich zwischen Basis- und Ziel-Commit durchführt und das Ergebnis als Output an nachfolgende Jobs weitergibt. So lässt sich die Filterlogik zentral pflegen, statt sie in jedem einzelnen Job zu wiederholen.
Diese Struktur skaliert deutlich besser als eine einzige monolithische Pipeline, insbesondere wenn ein Repository mehrere Magento-Module mit unterschiedlichen Verantwortlichkeiten und Test-Anforderungen enthält.
jobs:
changes:
runs-on: ubuntu-latest
outputs:
seosuite: ${{ steps.filter.outputs.seosuite }}
steps:
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
seosuite:
- 'src/app/code/Mironsoft/SeoSuite/**'
test-seosuite:
needs: changes
if: needs.changes.outputs.seosuite == 'true'
runs-on: ubuntu-latest
steps:
- run: echo "SeoSuite testen"
6. Tags und Releases als eigene Trigger
Neben Branches lassen sich auch Git-Tags als Trigger für eigene Workflows nutzen, typischerweise für Deployment- oder Release-Pipelines, die sich klar von der regulären Test-Pipeline eines Pull Requests unterscheiden sollen.
Ein an Semantic Versioning angelehntes Tag-Schema, etwa v2.4.0, lässt sich über ein passendes Muster im Trigger gezielt abgreifen, sodass ein Release-Workflow ausschließlich bei tatsächlichen Versions-Tags ausgelöst wird und nicht bei jedem beliebigen Tag im Repository.
Diese Trennung sorgt dafür, dass produktive Deployments an einen bewussten, versionierten Git-Vorgang gebunden sind, statt als Nebeneffekt eines gewöhnlichen Merges auf den Hauptzweig zu passieren.
on:
push:
tags:
- 'v[0-9]+.[0-9]+.[0-9]+'
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: echo "Deploye Version ${{ github.ref_name }}"
7. Concurrency-Gruppen für parallele Pull Requests
Wird derselbe Feature-Branch mehrfach kurz hintereinander gepusht, etwa nach kleinen Korrekturen aus dem Review, starten ohne weitere Konfiguration mehrere Pipeline-Läufe parallel, obwohl nur das Ergebnis des letzten Laufs relevant ist.
Über concurrency Gruppen mit cancel-in-progress lassen sich veraltete Läufe für denselben Branch automatisch abbrechen, sobald ein neuer Push eintrifft. Das spart nicht nur Compute-Minuten, sondern liefert dem Entwickler auch schneller ein aktuelles Ergebnis.
In Teams mit vielen parallelen Pull Requests über mehrere Zeitzonen hinweg reduziert diese Konfiguration die Warteschlange für Runner spürbar, da nicht mehr längst überholte Läufe unnötig Ressourcen belegen.
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
8. Commit-Konventionen als Pipeline-Input
Conventional Commits sind mehr als eine Stilfrage: Wenn Commit-Präfixe wie fix: oder feat: konsequent verwendet werden, kann eine Pipeline daraus automatisch Versionsnummern, Changelogs und sogar Release-Entscheidungen ableiten, ohne dass jemand manuell eine Versionsnummer festlegen muss.
Werkzeuge für semantische Releases lesen die Commit-Historie seit dem letzten Release aus und entscheiden anhand der Präfixe, ob es sich um ein Patch-, Minor- oder Major-Release handelt. Das setzt aber konsistente Commit-Messages im gesamten Team voraus, sonst wird die automatische Ableitung unzuverlässig.
Ein zusätzlicher Workflow, der Commit-Messages bereits im Pull Request auf das erwartete Format prüft, verhindert, dass inkonsistente Nachrichten erst später in der Release-Pipeline zu Problemen führen.
- name: Commit-Format prüfen
uses: wagoid/commitlint-github-action@v6
with:
configFile: .commitlintrc.json
9. Sicherheit: Secrets und Fork-Pull-Requests
Pull Requests aus Forks haben standardmäßig keinen Zugriff auf im Repository hinterlegte Secrets, was bei öffentlichen Repositories wichtig für die Sicherheit ist, in vielen Fällen aber legitime Workflow-Schritte wie Deployment-Vorschauen verhindert.
Der Trigger pull_request_target läuft im Kontext des Zielbranches und hat deshalb Zugriff auf Secrets, checkt aber standardmäßig auch nicht automatisch den Code des Forks aus. Ein unbedachtes explizites Auschecken des Fork-Codes kombiniert mit diesem Trigger öffnet ein ernstes Sicherheitsrisiko, da fremder Code dann mit vollen Rechten laufen kann.
Als Faustregel gilt: Für reine Tests und Checks reicht der normale pull_request Trigger vollkommen aus. pull_request_target sollte nur eingesetzt werden, wenn es unbedingt nötig ist, und dann ausschließlich mit sorgfältiger Prüfung, welcher Code tatsächlich mit welchen Rechten ausgeführt wird.
# Riskant: Fork-Code mit Zugriff auf Secrets ausführen
on:
pull_request_target:
jobs:
build:
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
# Ab hier läuft fremder Code mit vollen Repository-Rechten
| Trigger | Typischer Einsatz | Zugriff auf Secrets | Risiko bei Forks |
|---|---|---|---|
| push | Schnelle Rückmeldung nach dem Commit | Ja, im eigenen Repository | Gering |
| pull_request | Qualitätssicherung vor dem Merge | Nein bei Forks | Gering |
| pull_request_target | Deployment-Vorschauen mit Secrets | Ja, auch bei Forks | Hoch bei Fork-Checkout |
| workflow_dispatch | Manuell ausgelöste Aktionen | Ja | Gering |
| schedule | Wiederkehrende Aufgaben, z.B. nächtliche Builds | Ja | Gering |
Mironsoft
Git-Workflows, Branching-Strategien und CI-Hooks
Chaotische Git-Historie und unklare Branching-Regeln im Team?
Wir richten saubere Git-Workflows ein, klären Branching-Strategien fürs Team und automatisieren Qualitätschecks über Git-Hooks und CI-Pipelines, damit die Historie nachvollziehbar bleibt.
Workflow-Audit
Bestehende Branching-Strategie und Merge-Praxis auf Schwachstellen prüfen.
Hook-Automatisierung
Pre-Commit- und Pre-Push-Hooks für Linting, Tests und Commit-Konventionen einrichten.
Team-Schulung
Rebase, Cherry-Pick und Konfliktauflösung im Team praxisnah vermitteln.
10. Zusammenfassung
GitHub Actions
Trigger-Wahl
pull_request für Checks, push nur auf relevante Branches
Caching
Cache-Key aus hashFiles der Lockdatei ableiten
Concurrency
cancel-in-progress verhindert veraltete Parallel-Läufe
Sicherheit
pull_request_target nur mit sorgfältiger Prüfung nutzen