# Git advanced

Merge conflicts oplossen, werken met branches en pull requests, en de commando’s die je redden als je iets stuk hebt gedaan.

Multimedia & Creatieve Technologie · 2.1 Programming principles · Les 2 · Git advanced · versie 1.0.0 · 2026-09-28

Tags: git, github

Slides: <https://slides.mctantwerp.be/nl/programming-principles/03-git-advanced>

Speaker notes: <https://slides.mctantwerp.be/nl/programming-principles/03-git-advanced.notes.md>

<!-- slide 2 -->

## Agenda

1. Merge conflicts
2. Branches
3. Mergen en rebasen: fast-forward, merge commit, rebase
4. Pull requests
5. Flows en versies: GitHub flow, git flow, semver
6. SSH
7. .gitignore
8. Als het misloopt: stash, restore, revert, reset, reflog

<!-- slide 3 -->

## Wat je al kan

### Alleen

- `git clone` of `git init` om te starten
- `git add` zet klaar, `git commit` bewaart lokaal
- `git push` stuurt je commits naar origin
- `git status` zegt altijd wat er aan de hand is

### Samen

- `git pull` haalt op en merget in één beweging
- Origin gaat alleen vooruit, dus een push wordt geweigerd als je achterloopt
- Werkten jullie in andere bestanden of andere regels, dan lost Git het zelf op
- Zelfde regel, allebei aangepast: daar stopte het vorige week

<!-- slide 4 -->

## 1. Merge conflicts

<!-- slide 5 -->

### Wanneer Git het zelf doet

- **Automatisch**: Andere bestanden, of andere regels in hetzelfde bestand. Git plakt beide wijzigingen samen en maakt er een merge commit van. Je merkt er niets van.
- **Conflict**: Dezelfde regel, aan beide kanten anders. Of de ene past een bestand aan dat de andere verwijderd heeft. Git zet dan beide versies in je bestand en wacht.

> [!NOTE]
> **Een conflict is geen fout**
>
> Niemand heeft iets stuk gemaakt. Git weigert alleen te gokken welke van de twee versies de juiste is, want dat kan hij niet weten.

<!-- slide 6 -->

### Zo ziet het eruit

```console
$ git pull
From https://github.com/samserrien/tic-tac-toe
   c6c26b4..91d4e7b  main       -> origin/main
CONFLICT (content): Merge conflict in bord.txt
Automatic merge failed; fix conflicts and then commit the result.
$ git status
On branch main
You have unmerged paths.
 (fix conflicts and run "git commit")
 (use "git merge --abort" to abort the merge)
Unmerged paths:
       both modified:   bord.txt
```

**both modified**: jullie hebben allebei aan dat bestand gewerkt.

<!-- slide 7 -->

### De markers in je bestand

- **<<<<<<< HEAD**: Vanaf hier staat jouw versie, die van de branch waar je op staat. Current change.
- **=======**: De scheidingslijn. Boven jij, onder de andere kant.
- **>>>>>>> 91d4e7b**: Tot hier loopt de binnenkomende versie. Erachter staat waar ze vandaan komt: een branch of een commit.

> [!WARNING]
> Die zeven tekens zijn gewone tekst in je bestand. Laat je er eentje staan, dan commit je een bestand dat nergens meer op slaat. Ook je browser struikelt erover.

<!-- slide 8 -->

### Zelf oplossen

<!-- slide 9 -->

### Afwerken

```console
$ git add bord.txt
$ git status
On branch main
All conflicts fixed but you are still merging.
 (use "git commit" to conclude merge)
       modified:   bord.txt
$ git commit -m "Merge: beide zetten bewaard"
[main 3f9a2d1] Merge: beide zetten bewaard
$ git push
   91d4e7b..3f9a2d1  main -> main
```

> [!TIP]
> `git add` op een bestand met conflict betekent: ik heb gekozen, dit is de juiste inhoud.

<!-- slide 10 -->

### Als je er niet uit geraakt

#### Opnieuw beginnen

- `git merge --abort` zet alles terug zoals voor de pull. Je verliest niets van je eigen commits.
- Daarna opnieuw pullen als je er klaar voor bent. Het conflict komt terug, het gaat niet vanzelf weg.
- Twijfel je over wat er precies verschilt: `git diff` toont beide kanten van het conflict.

#### Eén kant nemen

- `git checkout --ours bord.txt` houdt jouw versie van dat bestand
- `git checkout --theirs bord.txt` houdt die van de andere kant
- Handig bij een bestand dat je toch opnieuw zou genereren, gevaarlijk bij echt werk: de andere kant verdwijnt volledig

<!-- slide 11 -->

### Met knoppen

**[GitHub Desktop](/nl/programming-principles/git-in-github-desktop)**: Na een pull met conflict krijg je dit venster. Oplossen doe je in je editor.

**[VS Code](/nl/programming-principles/git-in-vscode)**: Kleurt beide kanten en zet Accept current, incoming of both erboven.

> [!WARNING]
> Lees wat er aan beide kanten staat voor je kiest.

<!-- slide 12 -->

### Minder conflicten

- Pull voor je begint te werken, en pull opnieuw voor je pusht
- Kleine commits die één ding doen. Twee dagen werk in één commit botst met alles.
- Verdeel het werk per bestand of per component, niet per regel
- Zeg het in de groepschat als je aan een gedeeld bestand begint
- Spreek een formatter af (`.editorconfig`, Prettier). Anders herschrijft de ene zijn editor elk bestand dat de andere aanraakt.

<!-- slide 13 -->

### Je krijgt een merge conflict in style.css. Wat is waar?

- A. Je code is stuk en je moet de repo opnieuw clonen
- B. Git heeft beide versies bewaard en wacht tot jij kiest
- C. De laatste push wint automatisch
- D. Je moet je eigen wijzigingen weggooien

**Antwoord:** B. Git heeft beide versies bewaard en wacht tot jij kiest

Niets is weg. Beide versies staan tussen de markers in je bestand. Jij maakt er één geldige versie van, git add, git commit.

<!-- slide 14 -->

## 2. Branches

<!-- slide 15 -->

**Een branch is een aparte lijn commits.**

Je werkt verder zonder aan main te komen, en je brengt het er later in één keer bij.

<!-- slide 16 -->

### Je werkt al op een branch

```console
$ git branch
* main
$ git status
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
```

- **main**: De lijn die altijd werkt. Wat hier staat, kan je tonen aan een klant.
- **master**: Dezelfde branch, de oude naam. Sinds 2020 heet een nieuwe repo op GitHub main.

<!-- slide 17 -->

### Geen kopie van je project

- **Wat je ziet**: Je switcht naar een andere branch en je bestanden veranderen. Alsof je in een tweede kopie van je project staat.
- **Wat Git doet**: Elke versie zit al in een commit. Bij een switch schrijft Git je map om naar de commit waar die branch naar wijst.
- **Wat er bijkomt**: Eén naam die naar één commit wijst, en die bij elke commit die je maakt mee opschuift. Geen tweede map, geen tweede repo.

> [!TIP]
> Daarom duurt een branch maken geen seconde, ook in een repo van 2 GB. Er wordt niets gekopieerd.

<!-- slide 18 -->

### Wat er in .git staat

```console
$ ls .git/refs/heads
feature  main
$ cat .git/refs/heads/main
8f2a1c4e5b09d3a7f1c2b8e4d6a0f5c3b7e9d1a2
$ wc -c .git/refs/heads/main
      41 .git/refs/heads/main
$ cat .git/HEAD
ref: refs/heads/feature/nav
```

**Een branch**: Een bestandje met de sha van één commit erin. 41 bytes.

**Een tag**: Hetzelfde bestandje, maar het schuift niet mee. v1.2.0 blijft naar die ene commit wijzen, ook al commit je verder.

**HEAD**: Zegt op welke branch je nu staat. Committen gebeurt daar, en nergens anders.

<!-- slide 19 -->

### Waarom je niet alles op main doet

- Half afgewerkte code op main betekent een half afgewerkte site online
- Een feature die tegenvalt, gooi je weg door de branch te verwijderen. Geen spoor op main.
- Twee features tegelijk botsen niet meer met elkaar
- Je collega kan jouw werk bekijken voor het op main staat. Dat is een pull request.
- Een bug in productie fix je op een aparte branch, ook als je midden in iets anders zit

<!-- slide 20 -->

### Aanmaken en wisselen

```console
$ git switch -c feature/nav
Switched to a new branch 'feature/nav'
$ git branch
* feature/nav
  main
$ git switch main
Switched to branch 'main'
$ git switch -
Switched to branch 'feature/nav'
$ git branch -a
* feature/nav
  main
  remotes/origin/main
```

**switch -c**: Create. Zonder de -c moet de branch al bestaan.

**git branch**: Lijstje van je lokale branches. Het sterretje staat bij die van jou.

**switch -**: Terug naar waar je net vandaan kwam. Werkt zoals cd - in je terminal.

<!-- slide 21 -->

### Hetzelfde in GitHub Desktop

- Bovenaan bij **Current branch** staan je branches en de knop New branch. Dat is `git switch -c`.
- Een nieuwe branch vertrekt altijd van de branch waar je nu op staat
- Na je eerste commit wordt de blauwe knop **Publish branch**: `git push -u origin ...`
- Klikken op een andere branch in die lijst is een switch, met hetzelfde effect op je map

<!-- slide 22 -->

### Wat een switch met je map doet

- **01 HEAD verhuist**: Dat ene bestandje wijst nu naar de andere branch. `git status` zegt het, GitHub Desktop en VS Code tonen het ook.
- **02 Je map**: Bij een switch verschijnen en verdwijnen bestanden. Dat is normaal: ze zitten veilig in de commits van de andere branch.
- **03 Wat niet gecommit is**: Dat blijft staan en reist mee. Commit dus voor je switcht, of parkeer het met `git stash`.

<!-- slide 23 -->

### Een branch groeit naast main

<!-- slide 24 -->

### Je branch op GitHub zetten

```console
$ git push
fatal: The current branch feature/nav has no upstream branch.
hint: To push the current branch and set the remote as upstream, use
hint:     git push -u origin feature/nav
$ git push -u origin feature/nav
remote: Create a pull request for 'feature/nav' on GitHub by visiting:
remote:      https://github.com/samserrien/portfolio/pull/new/feature/nav
 * [new branch]      feature/nav -> feature/nav
$ git push
   4b7d9e2..a1c3f80  feature/nav -> feature/nav
```

De `-u` typ je één keer. Vanaf dan weet die branch waar hij hoort en volstaat `git push`.

<!-- slide 25 -->

### switch of checkout

#### git switch

- Sinds Git 2.23 (2019), doet één ding: van branch wisselen
- `git switch -c naam` maakt er eentje
- Duidelijker, en moeilijker om per ongeluk werk te overschrijven

#### git checkout

- De oude manier, doet nog altijd hetzelfde
- `git checkout -b naam` maakt er eentje
- Doet ook bestanden terugzetten (`git checkout -- file`), en dat is precies waarom ze het gesplitst hebben. In tutorials zie je nog vooral checkout.

<!-- slide 26 -->

### Even in een oude versie kijken

```console
$ git log --oneline
8f2a1c4 (HEAD -> main) Merge feature/nav
9e0b3a7 Fix footer link
20cfa4a Add index.html
$ git switch --detach 20cfa4a
HEAD is now at 20cfa4a Add index.html
You are in 'detached HEAD' state.
$ git switch -
Switched to branch 'main'
$ git restore --source=20cfa4a index.html
```

**detached HEAD**: HEAD wijst even naar een commit in plaats van naar een branch. Rondkijken mag, committen doe je hier beter niet.

**git switch -**: Terug naar waar je vandaan kwam. Je map staat weer zoals main.

**restore --source**: Haalt één bestand uit een oude commit, de rest blijft zoals het is.

<!-- slide 27 -->

### Namen van branches

| Naam | Waarvoor |
| --- | --- |
| `feature/contactformulier` | Iets nieuws dat je toevoegt |
| `fix/footer-link` | Een bug die je oplost |
| `hotfix/betaalknop` | Iets dat nu stuk is in productie |
| `MCB-193-login-scherm` | Met een ticketnummer, als je team met tickets werkt |
| `nieuwe-branch-2-final` | Zegt niets. Na een week weet niemand nog wat erin zit. |

> [!TIP]
> Kleine letters, streepjes, geen spaties, en niet langer laten leven dan een paar dagen.

<!-- slide 28 -->

### Zelf spelen

<!-- slide 29 -->

### Je staat op feature/nav en maakt drie commits. Wat staat er nu op main?

- A. Alle drie de commits
- B. Alleen de laatste commit
- C. Niets van die drie
- D. Dat hangt af van of je gepusht hebt

**Antwoord:** C. Niets van die drie

Commits gaan naar de branch waar HEAD staat. main blijft exact waar hij stond tot jij die branch mergt.

<!-- slide 30 -->

### Opdracht: Werken op een branch

*15 minuten, Individueel*

In je eigen portfolio-repo of in my-first-repo. Met de CLI of met GitHub Desktop, wat jij het liefst hebt.

1. Maak `feature/over-mij` en zet er minstens twee commits op.
2. Push de branch met `git push -u origin feature/over-mij` en kijk op GitHub of je hem ziet staan.
3. Switch terug naar main. Kijk in je map: je nieuwe werk is weg. Check `git log --oneline`.
4. Switch terug naar je branch en commit nog iets. Straks mergen we dit.

<!-- slide 31 -->

## 3. Mergen en rebasen

<!-- slide 32 -->

### Fast-forward

<!-- slide 33 -->

### Merge commit

<!-- slide 34 -->

### In je terminal

```console
$ git switch main
Switched to branch 'main'
$ git pull
Already up to date.
$ git merge --no-ff feature/nav
Merge made by the 'ort' strategy.
 1 file changed, 24 insertions(+)
$ git push
   9e0b3a7..8f2a1c4  main -> main
$ git branch -d feature/nav
Deleted branch feature/nav (was a1c3f80).
$ git push origin --delete feature/nav
 - [deleted]         feature/nav
```

<!-- slide 35 -->

### Mergen in GitHub Desktop

- Eerst bovenaan naar main switchen, dan Branch, **Merge into current branch**. Dat is `git merge feature/nav`.
- **Update from main** doet het omgekeerde: main binnenhalen op je feature branch.
- Daarna Push origin, anders staat de merge alleen bij jou
- Branch, Delete haalt de branch weg, en Desktop vraagt of hij hem ook op origin mag verwijderen

<!-- slide 36 -->

### Je branch bijwerken

- Hoe langer je branch leeft, hoe verder main wegdrijft
- Haal main regelmatig binnen op je branch. Dan los je drie kleine conflicten op in plaats van dertig grote.
- In GitHub Desktop heet dit Update from main, in de browser Update branch

```console
$ git switch feature/nav
$ git fetch origin
$ git merge origin/main
Merge made by the 'ort' strategy.
 index.html | 6 +++---
```

<!-- slide 37 -->

### Rebase

#### Wat het doet

- `git rebase main` op je feature branch neemt jouw commits en speelt ze opnieuw af bovenop de laatste commit van main
- Resultaat: een rechte lijn, geen merge commit, een geschiedenis die leest als een verhaal
- Je commits krijgen een nieuwe sha. Het zijn technisch nieuwe commits met dezelfde inhoud.
- Conflicten los je op per commit, dus je kan er meerdere na elkaar krijgen

#### De gouden regel

- Rebase nooit iets dat al gepusht en gedeeld is
- Je collega heeft die oude commits, jij herschrijft ze, en dan ziet zijn Git twee versies van hetzelfde werk
- Op je eigen branch die nog nergens staat: prima, en netter dan een merge
- Toch al gepusht en toch gerebased? Dan is `git push --force-with-lease` de minst gevaarlijke forceerknop. Zeg het tegen je team.

<!-- slide 38 -->

### git log --graph

```console
$ git log --oneline --graph --all
*   8f2a1c4 [green](HEAD -> main, origin/main) [dim]Merge branch feature/nav
|\
| * a1c3f80 [green](feature/nav) [dim]Add mobile menu
| * 4b7d9e2 [dim]Add nav bar
* | 9e0b3a7 [dim]Fix footer link
|/
* 20cfa4a [dim]Add index.html
* c6c26b4 [dim]Initial commit
```

> [!TIP]
> **Te lang om te typen**
>
> `git config --global alias.lg "log --oneline --graph --all"` en vanaf nu volstaat `git lg`.

<!-- slide 39 -->

### Wanneer maakt git merge een extra commit aan?

- A. Altijd, elke merge geeft een merge commit
- B. Alleen als er aan beide kanten commits bijgekomen zijn
- C. Alleen als er een conflict is
- D. Nooit, mergen verplaatst alleen labels

**Antwoord:** B. Alleen als er aan beide kanten commits bijgekomen zijn

Stond de doelbranch stil, dan schuift het label gewoon door: fast-forward, geen nieuwe commit. Zijn beide kanten vooruit gegaan, dan knoopt Git ze samen in een merge commit met twee ouders.

<!-- slide 41 -->

## 4. Pull requests

<!-- slide 42 -->

**Mergen doe je niet zelf.**

Op een echt project mag je meestal niet rechtstreeks naar main pushen. Je vraagt of je werk erbij mag, en iemand anders kijkt ernaar.

<!-- slide 43 -->

### Waarom een pull request

- Iemand anders leest je code voor ze op main staat. Dat vangt bugs die jij niet meer ziet.
- Tests en checks draaien automatisch op je branch
- De discussie over een keuze staat bij de code, niet in een chat die je over zes maanden niet meer terugvindt
- De eigenaar van een open source project houdt controle: iedereen mag voorstellen, hij beslist
- Het is de standaard workflow op GitHub, GitLab (daar heet het merge request) en Azure DevOps

<!-- slide 44 -->

### Zo ziet er eentje uit

<!-- slide 45 -->

### De flow

1. **Branch en push**: git switch -c feature/contact, werken, committen, git push -u origin feature/contact
2. **PR openen**: GitHub toont een knop Compare & pull request. In Desktop: Branch, Create pull request.
3. **Review**: Je collega leest, stelt vragen of vraagt wijzigingen. Jij commit en pusht, de PR volgt vanzelf.
4. **Merge en opruimen**: Groene knop, branch verwijderen, lokaal main pullen en git fetch --prune.

```console
$ git switch main
$ git pull
Updating 9e0b3a7..8f2a1c4
```

<!-- slide 46 -->

### Drie manieren om te mergen

- **Merge commit**: Alle commits van de branch blijven staan, plus een merge commit erbovenop. Mijn voorkeur: je ziet later nog exact wie wat wanneer gedaan heeft.
- **Squash and merge**: Alles wordt één commit op main, met de titel van je PR. Netter log, maar je tussenstappen zijn weg.
- **Rebase and merge**: Je commits schuiven één voor één bovenop main, zonder merge commit. Alleen in een team dat zijn commits klein houdt.

> [!NOTE]
> Welke van de drie mag, staat in de instellingen van de repo. In een team kiest men er meestal één en zet men de andere twee af.

<!-- slide 47 -->

### Conflicten in een pull request

GitHub lost dit niet voor je op. Je haalt main binnen op je branch, lost het conflict daar op en pusht opnieuw. De PR wordt vanzelf groen.

```console
$ git switch feature/contact
$ git fetch origin
$ git merge origin/main
CONFLICT (content): Merge conflict in index.html
$ git add index.html
$ git commit
$ git push
```

<!-- slide 48 -->

### Een review geven

#### Zo niet

- "Dit klopt niet."
- "Lelijk."
- Drie schermen commentaar over inspringen
- Approve zonder gekeken te hebben, omdat het vlot moet gaan

#### Zo wel

- "Deze functie wordt nergens gebruikt, mag die weg?"
- "Als email leeg is, crasht dit. Kan je dat opvangen?"
- Zeg ook wat je goed vindt. Een review is geen examen.
- Twijfel je: stel een vraag in plaats van een oordeel te vellen

<!-- slide 49 -->

### main beschermen

*Settings, Branches, Add branch ruleset*

- **Require a pull request**: Niemand pusht nog rechtstreeks naar main, ook de eigenaar niet. Alles passeert langs een PR.
- **Require approvals**: Minstens één andere persoon moet goedkeuren. In een groepswerk is dat een gezonde afspraak.
- **Require status checks**: De tests of de build moeten groen zijn voor de knop werkt.
- **Automatically delete head branches**: Na de merge verdwijnt de branch. Anders staan er na een maand veertig branches op je repo.

<!-- slide 50 -->

### Forks

1. **Fork**: Je maakt een kopie van de repo onder je eigen account. Die mag je wel aanpassen.
2. **Branch en commit**: Cloon je fork, maak een branch, doe je wijziging, push naar je fork.
3. **Pull request**: Vanuit je fork open je een PR naar de originele repo. De eigenaar beslist.

> [!TIP]
> **Lees eerst de regels**
>
> Veel projecten hebben een `CONTRIBUTING.md`. Een PR die de afspraken negeert, wordt gesloten zonder discussie.

<!-- slide 51 -->

### Opdracht: Pull request per twee

*25 minuten, Per 2*

Dezelfde repo als daarnet, nu met een review erbij. De pull request maak je op github.com, de rest mag met de CLI of met Desktop.

1. Voeg elkaar toe als collaborator, allebei in je eigen repo.
2. Persoon 1 maakt een branch met een kleine wijziging en opent een pull request naar main.
3. Persoon 2 leest de code en schrijft minstens twee opmerkingen. Eén ervan vraagt een echte wijziging.
4. Persoon 1 commit de aanpassing op dezelfde branch. Persoon 2 approvet en merget. Wissel daarna van rol.

<!-- slide 52 -->

## 5. Flows en versies

<!-- slide 53 -->

### Drie manieren van werken

- **GitHub flow**: main is altijd deploybaar. Elke feature een korte branch, PR, merge, branch weg. Dit is wat jullie gebruiken, en wat de meeste webteams doen.
- **main en develop**: Features gaan naar develop, en develop gaat naar main als er een release klaar is. Zinvol als je releases in blokken uitbrengt.
- **git flow**: main, develop, release/x.y.z, feature/, hotfix/. Uit 2010, voor software met versies op een schijf. Veel teams zijn ervan afgestapt omdat het zwaar weegt.

> [!NOTE]
> **Kies er één en schrijf ze op**
>
> De beste flow is die waar je team zich aan houdt. Zet ze in je README.

<!-- slide 54 -->

### release/0.5.0 in het echt

![Git-geschiedenis van een klantproject met een tiental gekleurde branches naast elkaar, waarin tickets zoals MCB-195 en MCB-215 in release/0.5.0 gemerged worden](https://slides.mctantwerp.be/decks/nl/programming-principles/03-git-advanced/branches-in-het-echt.jpg)

*Elke kleur is een branch. Elk ticket krijgt er eentje, en ze komen samen in release/0.5.0.*

<!-- slide 55 -->

### Een hotfix

<!-- slide 56 -->

### Semantic versioning

*MAJOR.MINOR.PATCH*

| Cijfer | Wanneer omhoog | Voorbeeld |
| --- | --- | --- |
| MAJOR | Bestaande code breekt. Wie updatet, moet werk doen. | 1.4.2 wordt 2.0.0 |
| MINOR | Iets nieuws erbij, alles blijft werken. | 1.4.2 wordt 1.5.0 |
| PATCH | Een bug opgelost, verder niets veranderd. | 1.4.2 wordt 1.4.3 |

Zowat alles wat je installeert nummert zo: Git zelf, VS Code, Node. Zie je ergens een sprong van 3.9.4 naar 4.0.0, dan weet je dat er iets gebroken is. Meer op [semver.org](https://semver.org).

<!-- slide 57 -->

### Tags en releases

```console
$ git tag -a v1.2.0 -m "Contactformulier en nieuwe nav"
$ git push origin v1.2.0
 * [new tag]         v1.2.0 -> v1.2.0
$ git tag
v1.0.0
v1.1.0
v1.2.0
```

- **Tag**: Een vast label op één commit. Een branch schuift mee, een tag nooit.
- **Release**: Op GitHub maak je van een tag een release, met uitleg en eventueel een zip. In Desktop: History, rechtsklik op een commit, Create tag.

<!-- slide 58 -->

## 6. SSH

<!-- slide 59 -->

### HTTPS of SSH

#### HTTPS

- Werkt meteen, ook achter een strenge firewall
- GitHub vraagt een token of opent je browser. Je wachtwoord werkt al jaren niet meer.
- Prima om te starten, vervelend zodra je tien keer per dag pusht

#### SSH

- Een sleutelpaar: de private key blijft op je laptop, de public key geef je aan GitHub
- Eén keer instellen, daarna nooit meer inloggen
- Dezelfde techniek waarmee je straks op een server geraakt

<!-- slide 60 -->

### Een sleutel maken

```console
$ ssh-keygen -t ed25519 -C "sam.serrien@kdg.be"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/Users/sam/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
$ ls ~/.ssh
id_ed25519  id_ed25519.pub  known_hosts
$ cat ~/.ssh/id_ed25519.pub
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIB9k2... sam.serrien@kdg.be
```

> [!WARNING]
> Alleen de `.pub` gaat naar buiten. Het bestand zonder .pub is je private key: die deel je met niemand.

<!-- slide 61 -->

### ed25519 of rsa

#### ed25519

- `ssh-keygen -t ed25519 -C "jouw mail"`
- Korte sleutel, snel, en sterk genoeg voor de komende jaren
- GitHub, GitLab en elke recente server slikken dit
- Neem deze, tenzij iets weigert

#### rsa 4096

- `ssh-keygen -t rsa -b 4096 -C "jouw mail"`
- De oude standaard, uit 1977. Werkt overal, ook op systemen die ed25519 niet kennen.
- Oudere servers, een NAS, een Jenkins of GitLab die al jaren niet geüpdatet is
- De `-b 4096` is geen detail: kortere RSA-sleutels zijn te zwak

<!-- slide 62 -->

### De sleutel bij GitHub zetten

*Settings, SSH and GPG keys, New SSH key*

1. **Kopiëren**: De volledige inhoud van id_ed25519.pub, van ssh-ed25519 tot je e-mailadres.
2. **Plakken**: Op github.com onder Settings, SSH and GPG keys. Geef hem een titel zoals "MacBook school".
3. **Testen**: ssh -T git@github.com in je terminal.

```console
$ ssh -T git@github.com
The authenticity of host 'github.com' can't be established.
Are you sure you want to continue connecting (yes/no)? yes
Hi samserrien! You've successfully authenticated, but GitHub does not provide shell access.
```

<!-- slide 63 -->

### Een bestaande repo omzetten

```console
$ git remote -v
origin  https://github.com/samserrien/portfolio.git (fetch)
origin  https://github.com/samserrien/portfolio.git (push)
$ git remote set-url origin git@github.com:samserrien/portfolio.git
$ git remote -v
origin  git@github.com:samserrien/portfolio.git (fetch)
origin  git@github.com:samserrien/portfolio.git (push)
$ git push
Everything up-to-date
```

Nieuw clonen doe je vanaf nu met de tab **SSH** onder de groene knop Code, niet meer met HTTPS.

<!-- slide 64 -->

### Opdracht: SSH instellen

*15 minuten, Individueel*

Eén keer doen, de rest van je opleiding plezier van hebben.

1. Maak een sleutel met `ssh-keygen -t ed25519 -C "jouw.mail@student.kdg.be"`
2. Zet de publieke sleutel bij GitHub onder Settings, SSH and GPG keys
3. Test met `ssh -T git@github.com` tot je je eigen username ziet staan
4. Zet één van je repos om met `git remote set-url` en push iets kleins

<!-- slide 65 -->

## 7. .gitignore

<!-- slide 66 -->

### Wat er nooit in hoort

*Eén patroon per regel, in de root van je repo*

*.gitignore*

```bash
# Komt terug met npm install
node_modules/

# Secrets
.env
*.key

# Build output
dist/

# Rommel van je OS
.DS_Store
```

- Alles wat je opnieuw kan genereren hoort er niet in. node_modules is zo een map van honderden megabytes.
- Secrets al helemaal niet. Wat ooit gepusht is, is publiek, ook nadat je het verwijdert.
- Grote media horen op een drive of in Git LFS, niet in je geschiedenis
- Het bestand zelf commit je wel: zo negeert iedereen in het project dezelfde dingen

<!-- slide 67 -->

### Die .env

- **Wat het is**: Een tekstbestandje met de sleutels en wachtwoorden die je code nodig heeft: een API key, een database wachtwoord, een tokentje van een dienst. Je programma leest het in bij het opstarten.
- **Waarom het lokaal blijft**: Die sleutels horen bij jouw machine, en vaak hangt er een factuur aan. Staat je `.env` op GitHub, dan kan iedereen op jouw rekening werken.

> [!TIP]
> **.env.example**
>
> Commit wel een kopie met lege waarden. Je collega ziet zo welke sleutels hij moet invullen, zonder dat jouw sleutels in de repo staan.

<!-- slide 68 -->

### Niet zelf uitvinden

- **[Bij het aanmaken](https://github.com/github/gitignore)**: GitHub vraagt bij een nieuwe repo welke template je wil: Node, Python, Unity, Laravel.
- **[gitignore.io](https://www.toptal.com/developers/gitignore)**: Typ je tools in en je krijgt een samengestelde lijst terug.
- **Vanuit je tool**: Rechtsklik op een bestand in GitHub Desktop en kies Ignore file. Dat voegt de regel voor je toe.

> [!WARNING]
> **Te laat toegevoegd?**
>
> Een bestand dat Git al volgt, blijft hij volgen. `git rm --cached .env` haalt het uit de repo, op je laptop blijft het staan. Staat het al op GitHub, dan is de inhoud gelekt: trek de sleutel in.

<!-- slide 69 -->

### Git LFS

```console
$ git lfs install
Git LFS initialized.
$ git lfs track "*.psd"
Tracking "*.psd"
$ git add .gitattributes poster.psd
$ git commit -m "Add poster"
$ git lfs ls-files
a1c3f80e2f * poster.psd
```

- Large File Storage: het grote bestand gaat naar een aparte opslag, in je repo blijft een verwijzing van een paar honderd bytes
- Zet het op voor je die bestanden commit. Achteraf moet je je geschiedenis herschrijven.
- `.gitattributes` commit je mee, anders volgt je collega dezelfde regel niet
- GitHub geeft 1 GB opslag en 1 GB verkeer per maand gratis, daarboven betaal je
- Zinvol voor video, PSD en game assets. Voor een website zelden nodig.

<!-- slide 70 -->

## 8. Als het misloopt

<!-- slide 71 -->

**Gecommit is gered.**

Wat gecommit is, krijg je bijna altijd terug. Wat je nooit gecommit hebt, is weg. Dat is het echte argument om vaak te committen.

<!-- slide 72 -->

### Ik wil terug

| Wat je wil | Commando | GitHub Desktop |
| --- | --- | --- |
| Mijn wijziging in dit bestand weggooien | `git restore index.html` | Rechtsklik, Discard changes |
| Iets uit de staging halen | `git restore --staged index.html` | Het vinkje uitzetten |
| Mijn laatste commit aanpassen | `git commit --amend` | History, rechtsklik, Amend commit |
| Een oude commit ongedaan maken, netjes | `git revert 4b7d9e2` | History, rechtsklik, Revert changes |
| Mijn laatste commit terug openbreken | `git reset --soft HEAD~1` | Rechtsklik, Undo commit |
| Even parkeren wat ik nu doe | `git stash` | Rechtsklik in Changes, Stash all changes |

`git commit --amend` en `git reset` herschrijven je geschiedenis. Alleen op commits die nog nergens gepusht zijn.

<!-- slide 73 -->

### git stash

```console
$ git stash
Saved working directory and index state WIP on feature/nav: a1c3f80
$ git status
nothing to commit, working tree clean
$ git switch main
$ git switch feature/nav
$ git stash pop
       modified:   nav.html
Dropped refs/stash@{0}
```

`git stash list` toont wat er geparkeerd staat, `git stash -u` neemt ook nieuwe bestanden mee. In Desktop staat het onder Stashed changes, links onderaan.

<!-- slide 74 -->

### revert of reset

#### git revert

- Maakt een nieuwe commit die het omgekeerde doet
- De oude commit blijft in de geschiedenis staan. Je ziet wat er gebeurd is en wat je teruggedraaid hebt.
- Veilig op een branch waar anderen op werken. Dit is wat je in een team gebruikt.

#### git reset

- `--soft` houdt je wijzigingen gestaged, `--mixed` zet ze terug in je map, `--hard` gooit ze weg
- Verplaatst het label van je branch naar achter. De commits erna wijst niemand nog aan.
- `--hard` is het enige commando in deze les dat echt werk kan vernietigen. Kijk twee keer.

<!-- slide 75 -->

### git reflog

```console
$ git reset --hard HEAD~2
HEAD is now at 20cfa4a Add index.html
$ git reflog
20cfa4a HEAD@{0}: reset: moving to HEAD~2
a1c3f80 HEAD@{1}: commit: Add mobile menu
4b7d9e2 HEAD@{2}: commit: Add nav bar
20cfa4a HEAD@{3}: commit: Add index.html
$ git reset --hard a1c3f80
HEAD is now at a1c3f80 Add mobile menu
```

> [!TIP]
> Voor je in paniek opnieuw cloont: tik `git reflog`. Negen op de tien keer staat je werk er nog.

<!-- slide 76 -->

### Nog een handvol

- **git cherry-pick**: Eén commit van een andere branch overnemen, zonder de rest.
- **git blame**: Per regel wie hem geschreven heeft, en in welke commit. Om context te zoeken, niet om iemand te pakken.
- **git bisect**: Zoekt binair de commit waarin een bug erin geslopen is.
- **git worktree**: Twee branches tegelijk, in twee mappen, uit dezelfde repo.
- **Hooks**: Scripts die vanzelf draaien voor een commit of een push, bijvoorbeeld een linter.
- **Git LFS**: Grote bestanden buiten je geschiedenis houden. Voor video en game assets.

<!-- slide 77 -->

### Je hebt per ongeluk een commit gepusht die de site stuk maakt. Wat doe je?

- A. git reset --hard en dan force pushen
- B. git revert van die commit, en pushen
- C. De repo verwijderen en opnieuw beginnen
- D. Een nieuwe commit die alles handmatig terugtypt

**Antwoord:** B. git revert van die commit, en pushen

Revert maakt een nieuwe commit die de wijziging omkeert. De geschiedenis blijft kloppen voor iedereen die al gepulld had. Een force push op een gedeelde branch breekt de repo van je collega's.

<!-- slide 78 -->

## 9. Opdrachten

<!-- slide 79 -->

### Opdracht: De opdracht van deze week

*Individueel*

Werk volgens de flow van vandaag, met de CLI of met GitHub Desktop.

1. Accepteer de uitnodiging en cloon de repo die je krijgt
2. Werk per onderdeel op een eigen branch met een duidelijke naam
3. Open per branch een pull request naar main, ook al werk je alleen. Schrijf erin wat je gedaan hebt.
4. Merge je eigen PR, verwijder de branch en pull lokaal
5. Klaar als main werkt en je geschiedenis een verhaal vertelt

<!-- slide 80 -->

### We are RARE

*people/jan_peeters.md*

```md
# Jan Peeters

* Hobby's: gamen, fotografie
* Interesses: motion design, CSS

> One finds limits by pushing them
```

*README.md*

```md
## Students

* [Alex Janssens](./people/alex_janssens.md)
* [Jan Peeters](./people/jan_peeters.md)
```

De repo: [github.com/mctantwerp/2pp-we-are-rare-2627](https://github.com/mctantwerp/2pp-we-are-rare-2627). Markdown vergeten? [Het deck over Markdown](/nl/general/01-markdown).

<!-- slide 81 -->

### Opdracht: Je eerste contribution

*25 minuten, Individueel*

Naar een repo die niet van jou is. CLI of GitHub Desktop, kies zelf.

1. Fork de klasrepo, cloon je fork en maak een branch
2. Maak `people/voornaam_achternaam.md`: H1, lijst, quote
3. Link ernaar onder **Students** in README.md, push, open een pull request
4. Conflict in README.md? Haal de nieuwste main van de klasrepo binnen en los het op

<!-- slide 82 -->

**Branch, push, pull request.**

Een branch kost niets, een pull request kost vijf minuten, en een conflict is een gesprek, geen ramp.

## Begrippen

- **Merge conflict**: Dezelfde regel is aan beide kanten anders aangepast, of de ene kant past een bestand aan dat de andere verwijderd heeft. Git zet beide versies in je bestand en wacht tot jij kiest. (slide 5)
- **Conflict markers**: <<<<<<< HEAD, ======= en >>>>>>> in je bestand bij een conflict. Boven de scheidingslijn staat jouw versie, eronder de binnenkomende. Het is gewone tekst, dus haal ze allemaal weg voor je commit. (slide 7)
- **git merge --abort**: Breekt een merge met conflict af en zet alles terug zoals voor de pull. Je verliest niets van je eigen commits, maar het conflict komt terug bij de volgende pull. (slide 10)
- **git checkout --ours en --theirs**: Lossen een conflict op voor een heel bestand: --ours houdt jouw versie, --theirs die van de andere kant. Wat de andere kant deed, verdwijnt volledig. (slide 10)
- **main**: De standaardbranch van een repo, de lijn die altijd werkt. Oudere repos en tutorials noemen hem master, sinds 2020 heet hij op GitHub main. (slide 16)
- **git switch**: Wisselt van branch: HEAD verhuist en Git schrijft je map om naar de commit waar die branch naar wijst. Wat niet gecommit is, blijft staan en reist mee. (slide 25)
- **git checkout**: De oude manier om van branch te wisselen, met git checkout -b om er eentje te maken. Het zet ook bestanden terug, en daarom kwam git switch er sinds Git 2.23 naast. (slide 25)
- **git push -u**: De eerste push van een nieuwe branch: git push -u origin feature/nav. De -u koppelt je branch aan die op origin, daarna volstaat git push. (slide 24)
- **Hotfix**: Een fix voor iets dat nu stuk is in productie, op een eigen branch (hotfix/...) vanaf main. Je feature blijft ondertussen onaangeroerd op zijn eigen lijn. (slide 55)
- **git merge**: Haalt de commits van een andere branch binnen op de branch waar je staat. Om een feature in main te krijgen: eerst git switch main en git pull, dan git merge feature/nav. (slide 34)
- **Fast-forward**: Een merge waarbij de doelbranch stilstond. Het label schuift gewoon door naar de laatste commit, zonder extra commit, en de geschiedenis blijft een rechte lijn. (slide 32)
- **Merge commit**: Een commit met twee ouders, die Git maakt als er aan beide kanten commits bijkwamen. Met git merge --no-ff krijg je er altijd een, zodat zichtbaar blijft dat de branch bestaan heeft. (slide 33)
- **Rebase**: git rebase main speelt de commits van je branch opnieuw af bovenop de laatste commit van main. Je krijgt een rechte lijn, maar ook nieuwe sha’s, dus rebase nooit iets dat al gepusht en gedeeld is. (slide 37)
- **Force push**: Een push die de branch op origin overschrijft met jouw versie, nodig na een rebase van iets dat al gepusht was. git push --force-with-lease is de minst gevaarlijke variant. Op een gedeelde branch breek je er de repo van je collega’s mee. (slide 37)
- **git log --graph**: Tekent je branches en merges als lijnen in de terminal. git log --oneline --graph --all toont alle branches, met een alias als git lg hoef je dat niet elke keer te typen. (slide 38)
- **Pull request**: Een vraag om je branch in main te mergen. Iemand anders leest je code voor ze op main staat, en de discussie blijft bij de code. Op GitLab heet het een merge request. (slide 43)
- **Review**: Een collega leest je pull request, stelt vragen of vraagt wijzigingen, en keurt goed met approve. Jij commit en pusht op dezelfde branch, de PR volgt vanzelf. (slide 45)
- **Squash and merge**: Alle commits van een pull request worden één commit op main, met de titel van de PR. Een netter log, maar je tussenstappen zijn weg. (slide 46)
- **Branch ruleset**: Regels voor een branch in de instellingen van je repo (Settings, Branches). Bijvoorbeeld: niemand pusht rechtstreeks naar main, alles gaat via een pull request met minstens één approval. (slide 49)
- **Fork**: Een kopie van een repo onder je eigen account, die je wel mag aanpassen. Je hebt er een nodig als je geen collaborator bent: je pusht naar je fork en opent een pull request naar de originele repo. (slide 50)
- **GitHub flow**: main is altijd deploybaar. Elke feature krijgt een korte branch en een pull request, na de merge gaat de branch weg. De flow van de meeste webteams. (slide 53)
- **git flow**: Een zware flow uit 2010 met main, develop, release/x.y.z, feature/ en hotfix/ branches. Bedoeld voor software met versies, veel teams zijn ervan afgestapt. (slide 53)
- **Semantic versioning**: Versienummers als MAJOR.MINOR.PATCH. MAJOR gaat omhoog als bestaande code breekt, MINOR bij iets nieuws dat alles laat werken, PATCH bij een bugfix. (slide 56)
- **Release**: Een tag op GitHub, met uitleg en eventueel een zip erbij. De tag zelf maak je met git tag -a v1.2.0 en push je met git push origin v1.2.0. (slide 57)
- **SSH**: Een manier om met GitHub te praten met een sleutelpaar in plaats van een token of je browser. Eén keer instellen, daarna nooit meer inloggen. De clone-url begint met git@github.com. (slide 59)
- **Private key**: Het deel van je SSH-sleutelpaar dat op je laptop blijft, het bestand zonder .pub (zoals ~/.ssh/id_ed25519). Die deel je met niemand. (slide 60)
- **Public key**: Het deel van je SSH-sleutelpaar dat je weggeeft, het bestand dat eindigt op .pub. Op GitHub zet je het onder Settings, SSH and GPG keys. (slide 60)
- **ed25519**: Het type SSH-sleutel dat je standaard maakt met ssh-keygen -t ed25519. RSA van 4096 bits neem je alleen voor oudere systemen die ed25519 niet kennen. (slide 61)
- **git remote set-url**: Verandert de url van origin, bijvoorbeeld van HTTPS naar SSH. git remote -v toont welke url er nu staat. (slide 63)
- **.gitignore**: Een bestand in de root van je repo met één patroon per regel voor wat Git moet negeren, zoals node_modules/ en .env. Het bestand zelf commit je wel, zo negeert iedereen dezelfde dingen. (slide 66)
- **.env**: Een tekstbestand met de sleutels en wachtwoorden die je code nodig heeft. Het blijft lokaal en staat in .gitignore, in de repo komt alleen een .env.example met lege waarden. (slide 67)
- **git rm --cached**: Haalt een bestand dat Git al volgt uit de repo, op je laptop blijft het staan. Nodig als je iets te laat in .gitignore zet. Stond het al op GitHub, dan is de inhoud gelekt en trek je die sleutels in. (slide 68)
- **Git LFS**: Large File Storage. Grote bestanden zoals video en PSD gaan naar een aparte opslag, in je repo blijft een verwijzing van een paar honderd bytes. Zet het op voor je die bestanden commit. (slide 69)
- **git restore**: git restore index.html gooit je wijziging in dat bestand weg. git restore --staged index.html haalt het uit de staging area, je wijziging blijft staan. (slide 72)
- **git commit --amend**: Past je laatste commit aan. Het herschrijft je geschiedenis, dus alleen op een commit die nog nergens gepusht is. (slide 72)
- **git stash**: Parkeert je wijzigingen zonder commit, zodat je map schoon is en je kan switchen. git stash pop haalt ze terug, git stash -u neemt ook nieuwe bestanden mee. (slide 73)
- **git revert**: Maakt een nieuwe commit die het omgekeerde doet van een oude. De oude commit blijft in de geschiedenis, dus dit is veilig op een branch die al gepusht is. (slide 74)
- **git reset**: Zet het label van je branch terug naar een eerdere commit, zoals git reset --soft HEAD~1. --soft houdt je wijzigingen gestaged, --mixed zet ze terug in je map, --hard gooit ze weg. Alleen voor commits die nog nergens gepusht zijn. (slide 74)
- **git reflog**: Houdt bij waar HEAD overal gestaan heeft, ook na een git reset --hard. Zo vind je de sha van een verloren commit terug en zet je je branch er weer op. (slide 75)
- **git cherry-pick**: Neemt één commit van een andere branch over, zonder de rest van die branch. (slide 76)
- **git blame**: Toont per regel wie hem geschreven heeft en in welke commit. Om context te zoeken, niet om iemand te pakken. (slide 76)
- **git bisect**: Zoekt binair naar de commit waarin een bug erin geslopen is. (slide 76)
- **git worktree**: Twee branches tegelijk uitgecheckt, in twee mappen, uit dezelfde repo. (slide 76)
- **Hooks**: Scripts die vanzelf draaien voor een commit of een push, bijvoorbeeld een linter. (slide 76)

## Links en bronnen

### In de presentatie

- [semver.org](https://semver.org) (slide 56)
- [Bij het aanmaken](https://github.com/github/gitignore) (slide 68)
- [gitignore.io](https://www.toptal.com/developers/gitignore) (slide 68)
- [github.com/mctantwerp/2pp-we-are-rare-2627](https://github.com/mctantwerp/2pp-we-are-rare-2627) (slide 80)
