Template pipeline
Nadat we hadden besloten om een automatische rebase uit te voeren, gingen we aan de slag met het schrijven van een script. Omdat deze rebase in meerdere Azure DevOps repository's uitgevoerd moet worden, gaan we dit script in een template yml file plaatsen. Deze template moet een checkout stap bevatten en een stap waarin een aantal git commando's uitgevoerd gaan worden.
Om het script toegang te geven tot de OAuth token, hebben we in de checkout stap de property persistCredentials op true gezet. Met de git commando's in het script zullen we de twee betreffende branches ophalen, een rebase uitvoeren en de changes weer pushen. Bij een rebase is het nodig om de push te forceren. We hebben ervoor gekozen om --force-with-lease te gebruiken. Dit is veiliger dan --force en is mogelijk omdat we wisten dat de geschiedenis van de development branch gelijk is aan wat we hebben gerebased.
Problemen en uitdagingen
Bij het ontwikkelen en testen van ons script liepen we tegen verschillende problemen en uitdagingen aan.
Rechten pipeline gebruiker
Allereerst bleek dat de gebruiker die door Azure wordt gebruikt om de pipeline uit te voeren (de Project Collection Build Service gebruiker), onvoldoende rechten had om de git commando's uit te voeren. Voor ons script waren de volgende rechten nodig:
- Contribute
- Bypass policies when pushing
- Force push (rewrite history, delete branches and tags)
Deze rechten kun je op verschillende niveaus zetten (op project-, repository- en branchniveau). Ga op het gewenste niveau naar ‘Project settings’ > 'Security' en zet de benodigde rechten op 'Allow' voor de Project Collection Build Service gebruiker.
Lokaal vs. Azure
Let op, er kan verschil zitten in hoe een rebase lokaal en in een Azure pipeline wordt uitgevoerd. Het kan zijn dat een rebase lokaal probleemloos verloopt, terwijl deze op Azure bijvoorbeeld Auto-merging errors geeft. Waarom een rebase in een pipeline faalt, is vaak niet meteen duidelijk. In ons geval was het probleem dat op Azure standaard niet een full fetch gedaan wordt waardoor alleen de recente geschiedenis gebruikt werd. Dit leidt bij een rebase al snel tot problemen. De oplossing is gelukkig simpel: voeg fetchDepth: 0 toe aan de checkout stap.
Reset branches
Om de rebase correct uit te voeren, moeten we van beide branches de laatste versie ophalen, dus ook van de master branch. We halen de laatste versie op met een git reset --hard om er zeker van te zijn dat we daar geen merge problemen hebben.
Foutafhandeling
Een rebase kan altijd mislukken, bijvoorbeeld door problemen zoals hierboven beschreven, maar er kan ook een merge conflict optreden. Zorg er in zulke gevallen voor dat de rebase netjes afgerond wordt, anders kan dit problemen veroorzaken bij de volgende run. Zo kan de volgende run een error geven dat het lijkt alsof er een rebase gaande is (omdat de map .git/rebase-merge nog bestaat). In dit geval kiezen we ervoor om een git rebase --abort uit te voeren zodat de rebase netjes wordt teruggedraaid en alles wordt opgeruimd. Dit doen we in een aparte stap en alleen als de rebase gefaald is. De git push die nodig is om daarna naar de development branch te pushen doen we daardoor ook in een aparte stap. We wilden graag dat de pipeline faalt als het rebasen of pushen mislukt. Daarom hebben we || exit 1 toegevoegd aan de rebase en push commando's.