jem-id
Terug
tech blog

Automatisch rebasen

Auteur
Gepost op 18 jun 2025
door Christian Hutter

Wat was het probleem?

Voor het product GreenCommerce dat we ontwikkelen voor de AGF-sector, gebruiken we een Bèta versie voor interne testdoeleinden. Deze Bèta versie is gebaseerd op onze development branch. Voorheen haalden we ontwikkelingen die intern getest moesten worden via een cherry-pick naar deze branch. Daarnaast werkten we een keer in de zoveel tijd de development branch bij met de master branch. Soms deden we dit door de development branch te verwijderen en opnieuw te maken. Daardoor raakten ontwikkelingen die wel in development zaten, maar nog niet in master, uit de Bèta versie verloren.

We vonden dat dit beter kon, daarom wilden we een script maken om alle aanpassingen automatisch uit de master branch naar de development branch te brengen. Deze oplossing is een stuk minder foutgevoelig. Daarnaast maakt het afgeronde ontwikkelingen sneller beschikbaar in de Bèta versie zonder dat er een ontwikkelaar voor nodig is. Alleen als we ontwikkelingen eerder intern willen testen dan ze in de master branch zitten, is er nog een ontwikkelaar nodig.

Rebase of merge?

Voordat we het script konden maken, wilden we eerst bepalen of het een git rebase of een git merge zou moeten uitvoeren. Welke aanpak het beste werkt, hangt af van de use case. Wij kozen voor een rebase om de volgende redenen:

  • de development branch wordt door geen enkele ontwikkelaar lokaal gebruikt (niemand heeft dus last van een veranderende geschiedenis)
  • we houden de geschiedenis van de development branch clean en hebben geen last van (lege) merge PR's

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.

Het uiteindelijke script

De uiteindelijke yml van onze Azure pipeline template ziet er bij ons als volgt uit:

parameters:
  - name: sourceBranch
    displayName: Branch to rebase onto
    type: string
    default: 'master'

  - name: targetBranch
    displayName: Branch to rebase
    type: string
    default: 'development'

jobs:
- job: RebaseBranch
  displayName: Rebase ${{ parameters.targetBranch }} onto ${{ parameters.sourceBranch }}
  steps:
  - checkout: self
    persistCredentials: true
    clean: false
    fetchDepth: 0
  - script: |
      git config set user.name "Autorebase pipeline"
      git config set user.email "autorebase@yourcompany.com"
      git fetch origin ${{ parameters.sourceBranch }}
      git checkout ${{ parameters.sourceBranch }}
      git reset --hard origin/${{ parameters.sourceBranch }}
      git fetch origin ${{ parameters.targetBranch }}
      git checkout ${{ parameters.targetBranch }}
      git reset --hard origin/${{ parameters.targetBranch }}
      git rebase origin/${{ parameters.sourceBranch }} || exit 1
    displayName: Git rebase
  - script: git rebase --abort
    displayName: Abort rebase
    condition: failed()
  - script: git push origin ${{ parameters.targetBranch }} --force-with-lease || exit 1
    displayName: Git push
  - script: git status
    displayName: Show git status
    condition: always()

Deze template gebruiken we in meerdere projecten en repository's om elke nacht de development branch bij te werken zodat er iedere ochtend een nieuwe Bèta versie beschikbaar is met daarin de laatste ontwikkelingen.

Ook werken met de nieuwste technieken? We zijn altijd op zoek naar talent!

Bekijk de openstaande vacatures of doe een open sollicitatie.