Git er et versionsstyringssystem. Det holder styr på ændringer i et projekt, så man kan gemme versioner, se historikken og gå tilbage til tidligere versioner. Git kan også bruges til at samarbejde om den samme kode.
GitHub er en tjeneste, hvor et Git-repository kan ligge online. Git og GitHub er altså ikke det samme: Git er versionsstyringen, mens GitHub blandt andet kan fungere som et fælles sted at gemme og dele et repository.
Hurtig reference
git init # initialize repository
git clone https://github.com/user/repo # clone repository
git status # show working tree status
git add file.txt # stage file
git add . # stage all changes
git commit -m "message" # commit changes
git push origin main # push to remote
git pull # pull latest changes
git log --oneline # compact commit history
git diff # show unstaged changes
git branch # list branches
git checkout -b feature # create and switch to branch
git checkout main # switch branch
git merge feature # merge branch into current branch
git branch -d feature # delete local branch
git tag v1.0 # create a tag
git push origin v1.0 # push tag to remote
dotnet new gitignore # opretter .gitignore som passer
# til .NET (hvis solution i solution folder, hvis projekt i projektfolder)
1. Hvad er det egentlig Git holder styr på?
Forestil dig en udvikler, der arbejder i en helt almindelig teksteditor. Teksteditoren ved ikke noget om Git, GitHub eller branches. Den åbner bare filer, og udvikleren ændrer dem.
Git ligger ved siden af projektet og holder styr på projektets versioner. Det er derfor nyttigt at skelne mellem fire ting:
- Working tree – de filer udvikleren aktuelt arbejder på.
- Staging area – de ændringer, der er valgt til næste commit.
- Local repository – Git's lokale historik af commits.
- Remote repository – for eksempel et repository på GitHub.
En helt grundlæggende arbejdsgang ser derfor sådan ud:
arbejd i teksteditor
↓
git status
↓
git add
↓
git commit
↓
git push
↓
remote repository
Det er vigtigt at forstå, at en commit ikke betyder "send til
GitHub". En commit gemmer en version i det lokale repository.
push sender derefter commits til det remote repository.
2. Story: Udvikler A arbejder alene
Udvikler A har sit eget projekt. Han arbejder direkte i sin teksteditor. Han starter med at gøre projektmappen til et Git-repository:
git init
Han arbejder videre, og når han har lavet en ændring, som han gerne vil gemme som en version, kan han gøre:
git status
git add .
git commit -m "Add player movement"
Han har nu en ny version i sit lokale Git-repository. Hvis projektet også har et remote repository på GitHub, kan han sende committen dertil:
git push origin main
Nu findes versionen både lokalt og på GitHub.
3. A får en risikabel idé
En dag får A en idé, som kræver store ændringer i programmet. Han ved ikke, om idéen bliver god.
Han kunne bare begynde at ændre main, men så risikerer han at
ødelægge den version, der allerede virker.
I stedet opretter han en branch:
git checkout -b experimental-feature
Nu arbejder han på experimental-feature.
Hans eksisterende main bliver liggende, som den var.
Han kan derfor tænke på branchen som en separat udviklingslinje:
main
|
A---B---C
\
D---E---F
experimental-feature
Situation A: Idéen virker
Efter nogle dage viser det sig, at idéen er god. A vil have ændringerne
ind i main.
git checkout main
git merge experimental-feature
git push origin main
Nu indeholder main også ændringerne fra eksperimentet.
Når branchen ikke længere er nødvendig, kan A slette sin lokale branch:
git branch -d experimental-feature
Situation B: Idéen var dårlig
Efter nogle dage opdager A, at idéen ikke fungerer. Han vil ikke have
ændringerne ind i main.
Han kan bare gå tilbage til main:
git checkout main
Hans eksperiment ligger stadig i branchens historik, men main
er uberørt.
Hvis han er sikker på, at eksperimentet ikke skal bruges, kan han slette branchen:
git branch -d experimental-feature
Det er en af de store fordele ved branches: Man kan eksperimentere uden at risikere at ødelægge den stabile udviklingslinje.
4. Branch eller tag?
Branch og tag har forskellige formål.
| Branch | Tag |
|---|---|
| En udviklingslinje | Et mærke på en bestemt commit |
| Bruges til videre arbejde | Bruges typisk til at navngive en bestemt version |
| Får normalt nye commits | Flyttes normalt ikke |
Til et eksperiment er en branch derfor det naturlige valg. Et tag er bedre til at markere en bestemt version, for eksempel:
v1.0
v1.1
v2.0
Man kan for eksempel gøre:
git tag v1.0
git push origin v1.0
5. Story: Linus og Will arbejder sammen
Nu har vi to udviklere.
Linus opretter et repository på GitHub. Han kloner det ned på sin computer:
git clone https://github.com/linus/my-project
Linus tilføjer derefter Will som collaborator på GitHub. Will får dermed adgang til repository'et og kan klone det:
git clone https://github.com/linus/my-project
Nu har Linus og Will hver deres lokale kopi af projektet. GitHub fungerer som deres fælles remote repository.
GitHub
remote repository
/ \
/ \
Linus Will
lokal lokal
kopi kopi
Det interessante er, at de faktisk kan komme langt uden branches.
De kan begge arbejde direkte på main, så længe de koordinerer
deres arbejde.
6. Linus og Will arbejder på forskellige dele
Antag, at projektet indeholder:
Program.cs
Player.cs
Enemy.cs
Linus arbejder på Player.cs, mens Will arbejder på
Enemy.cs.
Linus laver en ændring:
git add Player.cs
git commit -m "Improve player movement"
git push origin main
Will har imens arbejdet på sin lokale kopi. Før han sender sine ændringer til GitHub, henter han de nyeste ændringer:
git pull
Git opdager, at Linus har ændret Player.cs, mens Will har
ændret Enemy.cs.
De to ændringer kolliderer ikke, så Git kan normalt kombinere dem automatisk.
Will kan derefter committe og pushe:
git add Enemy.cs
git commit -m "Add enemy collision"
git push origin main
Projektet på GitHub indeholder nu begge udvikleres ændringer.
Det er en vigtig pointe: Git kan sammenflette mange ændringer automatisk, især når udviklerne har ændret forskellige dele af projektet.
7. Hvad hvis de ændrer den samme kode?
Senere kommer både Linus og Will til at ændre den samme funktion:
int CalculatePrice()
{
...
}
Linus ændrer funktionen og pusher sin commit til GitHub. Will har samtidig ændret den samme funktion ud fra den gamle version.
Will forsøger:
git push
Men Git afviser push'et, fordi GitHub nu indeholder commits, som Will ikke har lokalt.
Will skal først hente de nye ændringer:
git pull
Denne gang kan Git ikke nødvendigvis kombinere ændringerne automatisk. Der er en merge conflict.
8. Hvordan ser en merge conflict ud?
Git markerer konflikten i filen, for eksempel:
int CalculatePrice()
{
<<<<<<< HEAD
return price * 1.25;
=======
return price + tax;
>>>>>>> origin/main
}
Markeringerne viser, at der findes to forskellige versioner af den samme del af koden.
Git ved ikke, hvilken version der er rigtig. Det er udvikleren, der skal beslutte det.
Der kan være tre muligheder:
- Man vælger Linus' løsning.
- Man vælger Wills løsning.
- Man skriver en ny løsning, der kombinerer eller erstatter de to.
Efter at Will har løst konflikten i teksteditoren, gemmer han filen og fortæller Git, at konflikten er løst:
git add .
git commit -m "Resolved merge conflict"
git push
Det centrale princip er: Git kan opdage en konflikt, men Git kan ikke vide, hvilken løsning der er fagligt korrekt.
9. Hvorfor er det så nyttigt, selv uden branches?
Linus og Will har allerede fået noget meget vigtigt ud af Git: De kan dele et projekt og holde styr på, hvem der har ændret hvad.
De behøver ikke sende ZIP-filer til hinanden, kopiere filer frem og tilbage eller forsøge at huske, hvilken version der er den nyeste.
Git giver dem blandt andet:
- en historik over ændringer
- mulighed for at se forskelle mellem versioner
- mulighed for at hente hinandens ændringer
- mulighed for at kombinere ændringer
- mulighed for at opdage og løse konflikter
- mulighed for at gå tilbage til tidligere versioner
For et lille projekt med to udviklere kan dette være rigeligt. Men forestil dig nu, at projektet bliver meget større.
10. De professionelle Linus og Will
Linus og Will arbejder nu ikke længere på et lille skoleprojekt. De arbejder i en virksomhed med et stort projekt og måske 20 eller 50 andre udviklere.
Nu bliver det risikabelt, hvis alle skriver direkte i main.
En udvikler kan komme til at pushe kode, der ikke virker, mens andre
udviklere samtidig arbejder videre.
Derfor vil man ofte bruge branches mere systematisk.
Linus skal lave en ny funktion:
git checkout -b feature/player-movement
Will skal lave en anden:
git checkout -b feature/enemy-ai
Nu kan de arbejde uafhængigt:
main
|
+------------+------------+
| |
↓ ↓
feature/player-movement feature/enemy-ai
| |
Linus' kode Will's kode
De committer løbende på deres branches og pusher dem til GitHub.
Pull Request
Når Linus mener, at hans funktion er færdig, merger han ikke nødvendigvis
selv direkte til main.
I stedet opretter han en Pull Request på GitHub.
En Pull Request betyder i praksis:
"Jeg har lavet disse ændringer.
Vil I gennemgå dem og vurdere, om de skal ind i main?"
Andre udviklere kan nu:
- se præcis hvilke linjer der er ændret
- kommentere på koden
- foreslå ændringer
- godkende ændringerne
- kontrollere at automatiske tests består
Når ændringerne er godkendt, kan branchen merges til main.
11. Den professionelle arbejdsgang
En forenklet professionel arbejdsgang kan derfor se sådan ud:
main
|
+---- feature/player-movement
| |
| commits
| |
| push
| ↓
| Pull Request
| |
| code review
| |
| automated tests
| |
+---------- merge
↓
main
Det betyder ikke, at der kun findes én rigtig Git-arbejdsgang. Professionelle
teams bruger forskellige workflows. Men branches og Pull Requests er meget
almindelige, fordi de gør det lettere at kontrollere ændringer, samarbejde
og holde main stabil.
12. Den vigtigste model
Eleverne behøver ikke først lære Git som en lang liste af kommandoer. Det vigtigste er at forstå modellen.
GitHub
remote repository
↑
push
↑
lokal repository
↑
commit
↑
staging area
↑
add
↑
working tree
↑
teksteditor
Og når flere udviklere arbejder sammen:
GitHub
/ \
push pull
↑ ↓
Linus Will
lokal lokal
Git Git
\ /
\ /
fælles projekt
Git er altså ikke teksteditoren, og GitHub er ikke selve versionsstyringen. Git er det system, der holder styr på projektets versioner og ændringer, mens GitHub kan bruges som et fælles remote repository og som platform for samarbejde.
14. Fra simpel til professionel
| Lille projekt | Professionelt projekt |
|---|---|
| Linus og Will kan arbejde direkte på main | Udviklere arbejder typisk på branches |
| De koordinerer selv deres arbejde | Pull Requests bruges til at kontrollere ændringer |
| Git håndterer mange ændringer automatisk | Merge conflicts håndteres og gennemgås systematisk |
| Commit og push | Commit, push, Pull Request, review og merge |
| Få udviklere | Mange udviklere og ofte automatiske tests |
Den simple model er ikke forkert. Den er bare ikke nødvendigvis den model, man ville vælge, når antallet af udviklere, ændringer og krav til stabilitet bliver stort.