Git-workshop
Door Robin Wacanno en Wouter Petri; met dank aan Charlie
Voorbereiding
Herhaal deze voorbereidingen ook voor andere computers waar je later Git op wil gaan gebruiken.
Installeer git
Voer het volgende commando uit in de terminal van je besturingssysteem. In het geval van macOS moet je hiervoor wel Brew installeren (instructies zijn te vinden op https://brew.sh/). Mocht je een andere Linux-distro gebruiken (dus niet Ubuntu), installeer dan Git met de corresponderende package manager.
- Windows:
winget install Git.Git
- Linux (Ubuntu):
sudo apt install git
- macOS:
brew install git
Toevoegen SSH-sleutel aan Github
Om het gebruik van Git wat makkelijker te maken voegen we met de volgende stappen een SSH-sleutel toe aan je Github-account om jezelf aldaar te authoriseren.
- Check of je al een sleutel hebt met
cat ~/.ssh/id_rsa.pub
, als je geen foutmelding krijgt, sla dan stappen 1 en 2 over ssh-keygen -t rsa -b 4096
- Accepteer de standaard instellingen, tenzij je weet wat je doet
- Kopieer de volledige uitvoer van
cat ~/.ssh/id_rsa.pub
- Ga naar https://github.com/settings/keys en klik op
New SSH key
- Plak je klipbord in het
Key
vak. De titel wordt automatisch ingevuld.
Account toevoegen aan UvA-organisatie
Om specifieke repo's gekoppeld aan de opleidingen binnen de UvA te gebruiken moet je je aan de organisatie binnen Github van de UvA koppelen. Dit kun je doen door naar de volgende pagina te gaan als je ingelogd bent bij Github: https://github.com/orgs/UvA-FNWI-Students/sso
SSH-sleutel authoriseren bij organisatie
Om ervoor te zorgen dat je SSH-sleutel ook binnen repo's van de UvA-prganisatie gebruikt kunnen worden, moet deze daarvoor geauthoriseerd worden. Dit kun je doen op de pagina met de sleutels: https://github.com/settings/keys. Je klikt dan rechts van de betreffende sleutel in de lijst op de volgende knop en kiest voor de organisatie van de UvA:
Basis van Git
In het kort
Git is een systeem om makkelijk versiecontrole over bestanden te hebben. Omdat het werkt aan de hand van line-based diffs, is het zeer goed geschikt voor code (waaronder ook Markdown en LaTeX) en zeer slecht voor binaire data (bijvoorbeeld Word-bestanden).
Een Git-setup bestaat uit een of meerdere clients die verbinding maken met een server. Clients kunnen code opvragen van en sturen naar de server. Op de server is de code onderverdeeld over verschillende branches, waardoor meerdere clients tegelijk aan andere onderdelen van dezelfde codebase kunnen werken zonder dat ze elkaar telkens in de weg zitten.
Terminologie
- commit: De deltas van verschillende bestanden, gebundeld met een commit message dat zou moeten omschrijven wat die wijzigingen doen.
- delta, diff: de veranderingen aan een bestand ten opzichte van een vorige versie.
Achter de schermen
Git is opgebouwd uit verschillende stadia. Het eerste onderscheid dat te maken valt, is tussen local
en remote
. Vanzelfsprekend is local
waar je zelf mee bezig bent en remote
wat de server opgeslagen heeft. local
is op zichzelf weer onderverdeeld in drie stadia: de workspace
, de index
en de local repository
.
- De
workspace
ofworking directory
is de code zoals het op je schijf staat en in je editor ziet. - De
index
ofstaging area
houdt bij welke veranderingen er gemaakt zijn ten opzichte van de vorige commit.- Om Git deze index te laten bijwerken, doe je
git add <filename>
- Om alle bestanden in de huidige map toe te voegen is de
git add -A
shortcut
- Om Git deze index te laten bijwerken, doe je
- De
local repository
is een lokale kopie van de remote repository. Dit zorgt er voor dat je ook offline kan switchen van branches en commits kan maken. In derepository
staat de complete graaf van alle commits.- Om van de index een commit te maken, doe je
git commit
- Om de remote commits van de
remote respository
te halen, doe jegit pull
- Om de local commits naar de
remote repository
te sturen, doe jegit push
- Om van de index een commit te maken, doe je
.gitignore
Het is gebruikelijk om in de root van je repository een .gitignore
bestand te hebben staan. In dit bestand kan je regels aangeven welke bestanden niet automatisch aan de Git index worden toegevoegd. Voor repos met Java code staat hier bijvoorbeeld vaak *.class
in.
Om er voor te zorgen dat alle bestanden in een map op een paar na worden toegevoegd (bijvoorbeeld als je LaTeX gebruikt en alleen alle .tex bestanden wil bewaren), kan je iets als het volgende doen:
*
!.gitignore
!*.tex
Dit negeert alle bestanden in de map waarin dit bestand staat, behalve .gitignore
zelf en alle .tex
-bestanden.
Zie ook https://git-scm.com/book/en/v2/Git-Basics-Recording-Changes-to-the-Repository#_ignoring voor een complete uitleg.
Houd er rekening mee dat bestanden die al in de index staan, niet automatisch eruit worden weggehaald als je de .gitignore
aanpast.
Nieuwe repo maken
Dit kan het beste via https://github.com/new gedaan worden.
Simpele commando's
Cloning
Om lokaal te beginnen met het bewerken van een repository, zal je eerst een kloon van de remote repository moeten hebben. Hiervoor kun je op de pagina van de repo klikken op de groene Code<>-knop. Gebruik dan altijd de link die in het SSH-tabje staat (niet degene in HTTPS) anders wordt de SSH-sleutel die je als voorbereiding aan je account hebt toegevoegd niet gebruikt.
git clone git@github.com:[gebruikersnaam]/[repo-naam].git
Staging (basis)
Om Git op de hoogte te brengen van wijzigingen aan een bestand, moet dat bestand in de index gezet worden (ookwel "staging" genoemd)
git add <bestand 1> <bestand 2> ...
Voor als je in 1 keer alle bestanden in de huidige map wil toevoegen aan de index, is de volgende shortcut:
git add -A
of de volgende als je alles in de huidige map wilt toevoegen:
git add .
Commit
Om de verschillende wijzigingen die in de index staan samen te bundelen, moet je daar een commit van maken. Aan een commit geef je tevens een (kort) bericht mee om die set wijzigingen te omschrijven. Het is voor jezelf en je teamgenoten makkelijker om een paar omschrijvingen te lezen dan alle gewijzigde regels code (opnieuw) door te nemen.
git commit -m "implemented feature #42"
Push
Nadat alle wijzigingen met een bericht zijn voorzien in een commit, is het tijd om die commit naar de remote repository te sturen, zodat je teamgenoten kunnen zien wat je gedaan hebt en daarmee verder werken.
git push
Fetch/Pull
Om ervoor te zorgen dat je niet met oude code bezig bent nadat een teamgenoot veranderingen gemaakt hebt, zal je eens in de zoveel tijd de nieuwe commits van de remote repository moeten halen.
git fetch # alleen huidige branch
git fetch --all # alle branches
Dit haalt de wijzigingen echter alleen naar de local repository
, maar de workspace
blijft nog ongewijzigd. Om de workspace bij te werken naar de local repository
is het nodig om beide stadia samen te voegen met git merge
. Omdat de combinatie fetch + merge
zeer vaak gedaan wordt, is er een shortcut:
git pull
Geavanceerde commando's
Branchen
Een branch is een aftakking van een andere branch om elkaar niet in de weg te zitten. In grote projecten is het gebruikelijk om voor iedere nieuwe feature en voor iedere bugfix een aparte branch te maken. Dit maakt het zeer overzichtelijk wat er veranderd is aan de code, zodat reviewers opmerkingen kunnen geven.
Checkout
Om de huidige workspace te veranderen naar de laatste commit van een bepaalde branch, moet je van branch wisselen. Om bijvoorbeeld naar de feature-42
branch te wisselen, doe je:
git checkout feature-42
Create
Om een nieuwe branch feature-43
te maken, doe je
git branch -u origin/feature-43
Houd er wel rekening mee dat je daarna eerst nog naar de branch moet wisselen voor je er daadwerkelijk op kan gaan werken:
git checkout feature-43
Wil je deze twee handelingen combineren (een branch maken en ernaartoe overschakelen), dan kan dat zo:
git checkout -b feature-43
Branches samenvoegen
Rebase
Een rebase verplaatst het punt van afsplitsing. Dit zorgt er voor dat er geen nieuwe onnodige commits gemaakt worden en de commit-graph relatief netjes blijft. Probeer liever git rebase
dan git merge
te gebruiken.
```
Wissel eerst naar de nieuwe branch
git checkout feature-42
Rebase daarna naar de laatste commit van de oude branch
git rebase develop ```
Merge
Om feature-42
naar master
te mergen:
```
Let op! Wissel eerst naar de oude branch
git checkout master
Merge daarna de nieuwe branch erin
git merge feature-42 ```
Conceptueel
Stel dat je de master
branch hebt, die je vanaf commit C afsplitst naar branch feature-42
, daarna verschillende commits aan feature-42
toevoegt, en uiteindelijk de nieuwe feature terug naar master
wil brengen met git checkout master; git merge feature-42
. Git pakt dan de diff tussen commit C en de laatste commit van feature-42
en voert die uit op de laatste commit van master
.
Als op master
geen nieuwe wijzigingen zijn aan de regels code die feature-42
aanpast, gaat het mergen zonder problemen. Is er echter in de tussentijd iets veranderd, dan weet git niet welke veranderingen te kiezen. Als het origineel aaa
was, master ondertussen bbb
heeft, maar feature-42
zegt dat het eigenlijk ccc
moet zijn, kan je je voorstellen dat Git het lastig krijgt - welke branch heeft het in dit geval gelijk? Om dit conflict op te lossen, vraagt Git de developer om hulp.
Merge-conflicten
Merge-conflicten zijn te herkennen aan een uitvoer zoals het volgende, wanneer je probeert om een branch te mergen. (Vergeet niet dat git pull
ook een merge doet!)
Auto-merging <file>
CONFLICT (content): Merge conflict in <file>
Automatic merge failed; fix conflicts and then commit the result.
Het bestand ziet er dan als volgt uit:
``` <<<<<<< HEAD
git-workshop-2020-1
=======
git-workshop-2020-0
9c0d93eb7370dd716d4bc28b3bba55a766c89538 ```
Het deel tussen de <<<
en ===
is de staat van die regels code zoals het nu op de destination branch (meestal master
) is. Het deel tussen ===
en >>>
geeft aan wat de target branch heeft. Voor je gemak staat het ID van de commit message waarin die regel is aangepast er ook bij. Als je nuttige commit messages geschreven hebt, kan dat helpen om te bepalen welke regel de juiste zou moeten zijn.
Oplossen
Sommige IDE's hebben ingebouwde ondersteuning om het leven iets makkelijker te maken. Bijvoorbeeld in VS Code is het mogelijk om "Accept Current Change" te kiezen om niks te veranderen aan master, "Accept Incoming Change" om de wijzigingen van de branch over te nemen, en "Accept Both Changes" als het eigenlijk niet overlapt en je beide stukken code wel zou willen houden.
Andere editors kunnen dit vast ook. Het is geen reden om een editor war over te starten!
Zit je echter op een server moet je het via de commandline doen. Voorstanders van commandline editors zoals emacs, nano en vim (op alfabetische volgorde) kunnen hier een speciale keybinding voor hebben, maar er zijn ook aparte tools voor zoals Lazygit.
Git in VS Code
Veel mensen gebruiken tegenwoordig VS Code, en hier zit een gebruiksvriendelijke grafische git-integratie.
Toevoegen van een repository
Om te beginnen moet je natuurlijk een repo toevoegen. Dit kan op twee manieren.
Klonen
Via het Git-menu links kun je een bestaande repo clonen van bijv. Github, of een lokale map waar Git al geïnitialiseerd is openen.
Lokaal aanmaken
Als je een nieuwe repo lokaal wil aanmaken, kan dit ook via het Git-menu.
Stagen en committen
Aanpassingen die je maakt in de bestanden van je project worden bijgehouden en deze worden door VS Code in het Git-menu weergegeven. Hier kun je dan ook individuele wijzigingen enz. toevoegen aan een commit.
Pushen en pullen
Pushen en pullen gebeurt ook in hetzelfde menu. Je kan zelfs instellen dat wijzigingen automatisch gesynchroniseerd worden als ze lokaal of extern gecommit zijn.
Branches
Anders dan de voorgaande operaties vind je het menu on te branchen in de balk onderin. Dit laat zien in welke branch je momenteel bezig bent (dit zal in het begin main
zijn). Als je hierop klikt krijg je de optie om naar een van de reeds bestaande branches over te schakelen, of om een nieuwe branch aan te maken.
Verder oefenen
https://learngitbranching.js.org/
https://github.com/git-game/git-game
https://github.com/git-game/git-game-v2
https://www.w3schools.com/git/exercise.asp