Git Squash Commits: Een Handleiding Met Voorbeelden

“Vroeg committen, vaak committen” is een populaire mantra in de softwareontwikkeling bij het gebruik van Git. Door dit te doen, zorg je ervoor dat elke wijziging goed gedocumenteerd is, verbetert de samenwerking en maakt het gemakkelijker om de evolutie van het project te volgen. Dit kan echter ook leiden tot een overmaat aan commits.

Dit is waar het belang van commits squashen om de hoek komt kijken. Het squashen van commits is het proces van het combineren van meerdere commit-invoerders tot één coherente commit.

Bijvoorbeeld, stel dat we werken aan een functie voor het implementeren van een inlogformulier, en we maken de volgende vier commits:

Une fois que la fonctionnalité est terminée, pour le projet global, ces commits sont trop détaillés. Nous n’avons pas besoin de savoir à l’avenir que nous avons rencontré un bogue qui a été corrigé pendant le développement. Pour garantir un historique propre dans la branche principale, nous fusionnons ces commits en un seul commit :

Hoe Commits Samenvoegen in Git: Interactieve Rebase

La méthode la plus courante pour fusionner des commits est d’utiliser une rébase interactive. Nous le démarons en utilisant la commande :

git rebase -i HEAD~<number_of_commits>

Remplacez <number_of_commits> par le nombre de commits que nous voulons fusionner.

Dans notre cas, nous avons quatre commits, donc la commande est :

git rebase -i HEAD~4

L’exécution de cette commande ouvrira un éditeur de ligne de commande interactif :

Het bovenste gedeelte toont de commits, terwijl de onderkant reacties bevat over hoe commits samen te voegen.

We zien vier commits. Voor elk ervan moeten we beslissen welk commando we moeten uitvoeren. We letten op de pick (p) en squash (s) commando’s. Om deze vier commits samen te voegen tot één commit, kunnen we de eerste pickselen en de overige drie squaschen.

We passen de commando’s toe door de tekst voor elke commit te wijzigen, specifiek door pick te veranderen in s of squash voor de tweede, derde en vierde commits. Om deze aanpassingen te maken, moeten we de “INSERT”-modus ingaan in de opdrachtregelteksteditor door op de i-toets op het toetsenbord te drukken:

Na het indrukken van i, zal de tekst -- INSERT -- aan de onderkant verschijnen, wat aangeeft dat we de invoegmodus zijn ingegaan. Nu kunnen we de cursor verplaatsen met de pijltoetsen, tekens verwijderen en typen zoals we dat zouden doen in een standaard teksteditor:

Als we tevreden zijn met de wijzigingen, moeten we de insertmodus verlaten door op de Esc-toets op het toetsenbord te drukken. De volgende stap is om onze wijzigingen op te slaan en de editor te verlaten. Hiervoor drukken we eerst op de :-toets om het editor te signaleren dat we een opdracht willen uitvoeren:

Aan de onderkant van de editor zien we nu een semicolon : die ons uitnodigt om een opdracht in te voeren. Om de wijzigingen op te slaan, gebruiken we de opdracht w, wat staat voor “schrijven”. Om de editor te sluiten, gebruiken we q, wat staat voor “afsluiten”. Deze opdrachten kunnen gecombineerd worden en samen ingetikt worden wq:

Om de opdracht uit te voeren, drukken we op de Enter-toets. Deze actie zal de huidige editor sluiten en een nieuwe openen, waardoor we de commit-bericht voor de net samengeperste commit kunnen invoeren. De editor zal een standaardbericht weergeven dat bestaat uit de berichten van de vier commits die we samenvoegen:

Ik raad aan het bericht te wijzigen om de door deze gecombineerde commits aangebrachte wijzigingen nauwkeurig weer te geven – want het doel van het samenvoegen is tenslotte om een schone en gemakkelijk leesbare geschiedenis te behouden.

Om met de editor te interacteren en het bericht te bewerken, drukken we opnieuw op i om de bewerkingsmodus in te gaan en het bericht naar wens te bewerken.

In dit geval vervangen we het commitbericht door “Implementatie inlogformulier.” Om de bewerkingsmodus te verlaten, drukken we op Esc. Vervolgens slaan we de wijzigingen op door op : te drukken, de opdracht wq in te voeren en op Enter te drukken.

Hoe de Commitgeschiedenis Te Bekijken

Over het algemeen kan het moeilijk zijn om de gehele commitgeschiedenis te herinneren. Om de commitgeschiedenis te bekijken, kunnen we de opdracht git log gebruiken. In het genoemde voorbeeld, voordat we het samenvoegen uitvoeren, zou het uitvoeren van de git log-opdracht het volgende weergeven:

Om door de lijst van commits te navigeren, gebruik je de pijltoetsen omhoog en omlaag. Om af te sluiten, druk je op q.

We kunnen git log gebruiken om de succes van het squaschen te bevestigen. Het uitvoeren ervan na het squaschen zal een enkele commit met de nieuwe bericht weergeven:

Pushen van gesquashte commit

Deze opdracht zal werken op het lokale repository. Om het remote repository te bijwerken, moeten we onze wijzigingen pushen. Omdat we de commitgeschiedenis hebben gewijzigd, moeten we een force push uitvoeren met de --force optie:

git push --force origin feature/login-form

Force pushen zal de commitgeschiedenis op de remote branch overschrijven en mogelijk others die aan die branch werken verstoren. Het is een goede gewoonte om met het team te communiceren voordat je dit doet

Een veiligere manier om force push te doen, die het risico van verstoren van medewerkers vermindert, is in plaats daarvan de optie --force-with-lease te gebruiken:

git push --force-with-lease origin feature/login-form

Deze optie zorgt ervoor dat we alleen force pushen als de remote branch niet is bijgewerkt sinds onze laatste fetch of pull.

Specifieke commits samenvoegen

Stel dat we vijf commits hebben:

Stel dat we commits 1, 2 en 5 willen behouden en commits 3 en 4 willen samenvoegen.

Als we gebruik maken van interactieve rebase, worden commits die zijn gemarkeerd voor samenvoegen gecombineerd met de directe voorgaande commit. In dit geval betekent het dat we Commit4 willen samenvoegen zodat het samenkomen met Commit3.

Om dit te doen, moeten we een interactieve rebase starten die deze twee commits bevat. In dit geval volstaan drie commits, dus we gebruiken het commando:

git rebase -i HEAD~3

Vervolgens stellen we Commit4 in op s zodat het samengevoegd wordt met Commit3:

Nadat we dit commando hebben uitgevoerd en de commits hebben vermeld, merken we op dat commits 3 en 4 samengevoegd zijn terwijl de rest ongewijzigd blijft.

Samenpersen vanaf een specifieke commit

In het commando git rebase -i HEAD~3 is het deel HEAD een afkorting voor de meest recente commit. De syntaxis ~3 wordt gebruikt om een voorouder van een commit te specificeren. Bijvoorbeeld, HEAD~1 verwijst naar de ouder van de HEAD commit.

Bij een interactieve rebase worden de overwogen commits alle vooroudercommits die leiden tot de commit die in de opdracht is opgegeven. Merk op dat de opgegeven commit niet is inbegrepen:

In plaats van HEAD te gebruiken, kunnen we direct een commit hash opgeven. Bijvoorbeeld, Commit2 heeft een hash van dbf3cc118d6d7c08ef9c4a326b26dbb1e3fe9ddf, dus de opdracht:

git rebase -i dbf3cc118d6d7c08ef9c4a326b26dbb1e3fe9ddf

zal een rebase starten die alle commits overweegt die na Commit2 zijn gemaakt. Daarom, als we willen dat een rebase begint bij een specifieke commit en die commit meenemen, kunnen we de opdracht gebruiken:

git rebase -i <commit-hash>~1

Conflicten Oplossen bij het Samenvoegen van Commits

Als we commits samenvoegen, combineren we meerdere commit wijzigingen in één commit, wat kan leiden tot conflicten als wijzigingen overlappen of sterk van elkaar afwijken. Hier zijn enkele veelvoorkomende scenario’s waarbij conflicten kunnen optreden:

  1. Overlappende wijzigingen: Als twee of meer samengeperste commits dezelfde regels van een bestand of nauw verwante regels hebben aangepast, kan Git mogelijk niet automatisch deze wijzigingen verzoenen.
  2. verschillende wijzigingsstatus: Als één commit een bepaald stuk code toevoegt en een andere commit diezelfde code aanpast of verwijdert, kan het samenvoegen van deze commits leiden tot conflicten die moeten worden opgelost.
  3. Hernoemen en aanpassen: Als een commit een bestand hernoemt en latere commits wijzigingen aanbrengen aan de oude naam, kan het samenvoegen van deze commits Git verwarren, waardoor een conflict ontstaat.
  4. Wijzigingen in binaire bestanden: Binaire bestanden laten zich niet goed samenvoegen met tekstgebaseerde diff-tools. Als meerdere commits dezelfde binaire file wijzigen en we proberen ze samen te voegen, kan een conflict optreden omdat Git deze wijzigingen niet automatisch kan verzoenen.
  5. Complexe geschiedenis: Als de commits een complexe geschiedenis hebben met meerdere samenvoegingen, takken of herschrijvingen ertussen, kan het samenvoegen ervan leiden tot conflicten vanwege de niet-lineaire aard van de wijzigingen.

Bij het samenvoegen zal Git proberen elke wijziging één voor één toe te passen. Als het tijdens het proces conflicten tegenkomt, zal het stoppen en ons de mogelijkheid bieden om deze op te lossen.

Conflicten zullen worden gemarkeerd met conflictmarkers <<<<<< en >>>>>>. Om de conflicten aan te pakken, moeten we de bestanden openen en elk conflict handmatig oplossen door te kiezen welk deel van de code we willen behouden. 

Na het oplossen van conflicten, moeten we de opgeloste bestanden stageen met de opdracht git add. Vervolgens kunnen we de rebase voortzetten met de volgende opdracht:

git rebase --continue

Voor meer informatie over Git-conflicten, bekijk deze handleiding over hoe merge conflicten in Git op te lossen.

Alternatieven voor het samenvoegen met Rebase

Het commando git merge --squash is een alternatieve methode voor git rebase -i om meerdere commits samen te voegen tot één commit. Dit commando is vooral nuttig wanneer we wijzigingen van een tak naar de hoofdtak willen samenvoegen, waarbij alle afzonderlijke commits worden samengeperst tot één. Hier is een overzicht van hoe je kunt samenvoegen met git merge:

  1. Wij navigeren naar de doeltak waarin we de wijzigingen willen opnemen.
  2. Wij voeren het commando git merge --squash <branch-name> uit, waarbij <branch-name> wordt vervangen door de naam van de tak.
  3. Wij committen de wijzigingen met git commit om een enkele commit te maken die alle wijzigingen van de feature-tak vertegenwoordigt.

Bijvoorbeeld, stel dat we de wijzigingen van de tak feature/login-form willen integreren in main als een enkele commit:

git checkout main git merge --squash feature-branch git commit -m "Implement login form"

Deze zijn de beperkingen van deze aanpak in vergelijking met git rebase -i:

  • Granulariteit van controle: Minder controle over afzonderlijke commits. Met rebase kunnen we kiezen welke commits we samenvoegen, terwijl merge alle wijzigingen in één commit combineert.
  • Intermediate history: Bij het gebruik van merge gaat de individuele commitgeschiedenis van de feature branch verloren in de main branch. Dit kan het moeilijker maken om de incrementele wijzigingen die tijdens de ontwikkeling van de feature zijn aangebracht, te volgen.
  • Pre-commit review: Omdat het alle wijzigingen als één enkel wijzigingset stagest, kunnen we geen individuele commits beoordelen of testen voordat ze worden samengevoegd, in tegenstelling tot tijdens een interactieve rebase waarbij elke commit afzonderlijk kan worden bekeken en getest.

Conclusie

Het opnemen van frequente en kleine commits in de ontwikkelingsworkflow bevordert samenwerking en duidelijke documentatie, maar het kan ook de geschiedenis van het project verwarren. Het samenvoegen van commits brengt een balans aan, door belangrijke mijlpalen te behouden terwijl de ruis van kleine iteratieve wijzigingen wordt verwijderd.

Om meer te leren over Git, raad ik deze bronnen aan:

Source:
https://www.datacamp.com/tutorial/git-squash-commits