Home

Git-workshop

Door Robin Wacanno en Wouter Petri; met dank aan Charlie


xkcd 1597

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.

  1. 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
  2. ssh-keygen -t rsa -b 4096
  3. Accepteer de standaard instellingen, tenzij je weet wat je doet
  4. Kopieer de volledige uitvoer van cat ~/.ssh/id_rsa.pub
  5. Ga naar https://github.com/settings/keys en klik op New SSH key
  6. 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:

image

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 verschillende stadia van git

  • De workspace of working directory is de code zoals het op je schijf staat en in je editor ziet.
  • De index of staging 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
  • 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 de repository 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 je git pull
    • Om de local commits naar de remote repository te sturen, doe je git push

.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 &lt;bestand 1&gt; &lt;bestand 2&gt; ...

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

branching

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

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 &lt;file&gt; CONFLICT (content): Merge conflict in &lt;file&gt; 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 &lt;&lt;&lt; en === is de staat van die regels code zoals het nu op de destination branch (meestal master) is. Het deel tussen === en &gt;&gt;&gt; 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.

VS Code 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.

github-clone

Lokaal aanmaken

Als je een nieuwe repo lokaal wil aanmaken, kan dit ook via het Git-menu.

scm-init-publish

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.

scm-staging

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.

sync

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.

branch-indicator

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