Git Rebase: Strömlinjeformad Kodhistorik

Squeeds Julkalender | 2023-12-13 | Erik Sikander
Versionshantering utgör en central del av programutveckling för att effektivt kunna samarbeta, och Git har blivit en standard för många utvecklingsteam. Medan `git merge` är ett vanligt använt kommando för att integrera förändringar från andra brancher erbjuder `git rebase` ett alternativ som kan resultera i en renare och mer strömlinjeformad kodhistorik.
ÅRETS KALENDER FRÅN OSS PÅ (2).png

Många har säkert hört talas om `git rebase` i Git, och en del av er är kanske redan flitiga användare av kommandot. Vissa har möjligen också hört att `git rebase` betraktas som "farligt" att använda, eftersom det trots allt handlar om att skriva över historiken. Och ja, det kan vara riskabelt om man inte förstår vad som händer och om det används i fel sammanhang. Så låt oss först få en förståelse för vad `git rebase` gör jämfört med `git merge`.

Git Merge

`git merge` kombinerar förändringar från en branch till en annan. När merge-kommandot används skapas en ny så kallad merge commit, vilket bevarar commit-historiken för båda brancherna. Merge commiten sparar även eventuella förändringar som behövs för att lösa eventuella kodkonflikter. Detta resulterar i att historiken byggs på och integreras sömlöst. Observera att detta beteende ofta eftersträvas, särskilt inom en master-branch. I det följande exemplet ser vi hur det ser ut när en `git merge` genomförs från master-branchen till en feature-branch.

A simple merge using `git merge master` from the feature branch

Som vi kan se i exemplet ovan har en extra merge-commit skapats och är markerad med gul färg. Om vi tar exemplet ett steg längre och utför ytterligare commits i feature-branchen för att sedan göra en `git merge` in i master, skulle det se ut som på bilden nedan.

Merge feature branch back into master using `git merge feature` from master-branch

För samarbete i remote-repo är `git merge` ett säkert sätt att hantera historiken, eftersom kommandot alltid lägger till ny historik utan att ändra något existerande. I större projekt där flera arbetar i samma branch är `git merge` det rekommenderade arbetssättet för att minimera potentiella problem.

 

Git Rebase


`git rebase` möjliggör skapandet av en ren och strömlinjeformad historik. Vid användning av `git rebase` flyttar man basen för branchen och applicerar varje commit i branchen på den nya basen i den ursprungliga ordningen. Eventuella konflikter måste lösas i varje commit där konflikten uppstår. Det resulterar i att vi behåller samma antal commits, och varje commit inkluderar de nödvändiga ändringarna för att lösa konflikterna. Nedan har vi ett exempel som illustrerar resultatet av `git rebase` när det utförs från feature-branchen mot master-branchen.

Ett enkelt rebase example där man kör kommandot `git rebase master` från feature branchen

Notera hur commit C har flyttats från att utgå från commit A till commit B, vilket innebär att basen för feature-branchen har bytts till commit B. Varje commit, i detta fall endast commit C, har sedan applicerats på den nya basen. Om vi därefter gör ytterligare en commit i feature-branchen för att sedan integrera den i master-branchen, kan man använda sig av kommandot `git merge --ff-only`. “ff” står för fast forward, vilket innebär att man endast tillåter att branch-pekaren flyttas “framåt” i historiken. På så sätt får man en historik som nedan.

Ett merge exempel där vi kör `git merge --ff-only feature` från master branchen


Arbetar man i en branch som är kopplad till en remote-repository kommer man inte kunna göra en vanlig `git push` efter en `rebase`. `git push` tillåter inte omskrivningar av historiken, så man måste lägga till en `--force` flagga. `--force` flaggan tvingar upp våra ändringar till remote-repot, vilket innebär att vi skriver över den befintliga historiken i remote-repot med hur branchen ser ut i den lokala branchen. Det är därför viktigt att förstå vad som händer för att undvika att förstöra arbetet som ens kollegor har utfört. Om flera utvecklare arbetar på samma branch kan det hända att man skriver över en commit som en annan utvecklare gjort, exempelvis om man inte har den senaste commiten i sin lokala branch vid rebase-tillfället. Därför är det inte rekommenderat att använda `git rebase` i samarbets-brancher om man inte kommunicerar och koordinerar `rebase` med alla andra som arbetar på samma branch.

Sammanfattning

Nu har vi utforskat både `git merge` och `git rebase` mer ingående, så låt oss sammanfatta för- och nackdelarna med dessa olika metoder.

Fördelar med `git merge`:

  • Bevarar en detaljerad commit-historik
  • Följer enbart en riktning i historiken, framåt. Ingen risk för att skriva över historiken då användning av `--force`-flaggan inte krävs
  • Lättare att samarbeta med flera utvecklare på samma branch i ett remote-repository.

Fördelar med `git rebase`:

  • Skapar en linjär och renare historia, vilket förbättrar läsbarheten.
  • Undviker onödiga merge commits, vilket förenklar tidslinjen.

Även om `git rebase` kan förbättra historiken medför det också risker vid samarbete i samma branch. För att minimera dessa risker bör man följa dessa riktlinjer:

  1. `git push --force` på master-branchen eller andra viktiga brancher är TOTALT förbjudet. Många git-verktyg, som GitHub eller GitLab, har inställningar som förhindrar force push.
  2. Se alltid till att ha den senaste versionen från remote-repository innan du utför en `git rebase`. Gör alltid en `git pull` innan `rebase`.
  3. Vid samarbete med andra utvecklare i samma branch, undvik att använda `rebase`
  4. Om flera utvecklare arbetar på samma branch och ändå vill använda `rebase` bör alla inblandade tillsammans utföra `rebase` och uppdatera sina lokala brancher direkt därefter.

Extra: `git merge --squash`

Ett annat arbetssätt som är värt att nämna, och som också ger en distinkt och överskådlig historik, är att använda `git merge --squash` när man integrerar en feature-branch. Detta kommando samlar alla förändringar från feature-branchen och lägger in dem i nuvarande branch utan att skapa individuella commits. Istället skapas en enda commit som innehåller alla ändringar från merge-operationen. Denna commit representerar därmed en sammanfattning av hela feature-branchen.

Att använda `--squash` kan vara fördelaktigt för att skapa en mer koncis och läsbar historik, särskilt när feature-branchen innehåller flera små commits. Det minimerar antalet commits som adderas till huvudhistoriken och kan göra det enklare att förstå historiken i ett ögonkast.

Nedan ser du ett exempel som illustrerar hur det kan se ut efter att ha genomfört en squash (innan man tagit bort feature-branchen). Notera att commit F innehåller alla ändringar som commit C, D och E innehåller.

Ett example hur squash funkar