Git CLI — fra lokal kode til samarbejde

Versionsstyring fra kommandolinjen (CLI) — lokalt repository til GitHub

CLI betyder Command Line Interface — kommandolinjen (Terminal, PowerShell, cmd). Kommandoerne her skrives som tekst, ikke via knapper i en GUI. Er du ny: start roligt, kopiér én kommando ad gangen, og læs hvad den gør, før du kører den.

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:

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:

  1. Man vælger Linus' løsning.
  2. Man vælger Wills løsning.
  3. 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:

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:

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.