Eure Klasse baut zusammen ein Quiz: Jede und jeder schreibt eine Runde Fragen, alle in einem gemeinsamen Git-Repository. Dabei passiert, womit Teams jeden Tag umgehen: Ein Push wird abgelehnt, zwei ändern dieselbe Zeile, ein Konflikt will gelöst werden. Danach verteilt ihr die Arbeit am Programm, und jede und jeder erledigt eine Aufgabe in einem eigenen Branch.
Allein an einem Programm zu arbeiten ist einfach: Was in deinem Ordner liegt,
gilt. Arbeiten zwanzig Leute an demselben Programm, braucht es eine Stelle, an
der alles zusammenkommt, und Regeln, damit niemand die Arbeit der anderen
überschreibt. Diese Stelle ist ein gemeinsames Git-Repository: Jede und
jeder hat einen Klon davon, arbeitet darin, holt sich die Commits der anderen
mit git pull und gibt die eigenen mit git push ab.
Euer Repository liegt auf dem Git-Server git.example.org. Darin liegt schon das Quiz: ein kleines Programm, eine Runde zum Ausprobieren und eine Liste mit Aufgaben. Deine Lehrkraft lädt dich auf den Server ein; mit der Einladung kannst du dort mitmachen.
Die Befehle für jeden Tag stehen im Handbuch unter Git, was ein Git-Server dazu braucht unter Git-Server.
Mit einem SSH-Schlüssel meldet sich dein Workspace beim Git-Server an, ohne dass du jedes Mal ein Passwort eingibst. Melde dich zuerst im Browser auf git.example.org an (das ist ein Beispiel: Welchen Server deine Klasse benutzt, sagt dir deine Lehrkraft). Prüfe dann im Terminal, ob du schon einen Schlüssel hast:
ls ~/.ssh/id_ed25519.pub
Erscheint No such file or directory, erzeugst du einen neuen. Bestätige den vorgeschlagenen Speicherort mit Enter; die Passphrase darfst du im Workspace leer lassen und noch zweimal Enter drücken:
ssh-keygen -t ed25519 -C "Vorname Nachname"
Zeige nun den öffentlichen Schlüssel an und kopiere die ganze Zeile, die mit ssh-ed25519 beginnt:
cat ~/.ssh/id_ed25519.pub
Trage ihn auf dem Git-Server ein. Wo das geht, hängt vom Server ab:
| GitLab | Profilbild oben rechts → Edit profile → Access → SSH keys → Add new key |
|---|---|
| Gitea, Forgejo | Profilbild oben rechts → Settings → SSH / GPG Keys → Add key |
| GitHub | Profilbild oben rechts → Settings → SSH and GPG keys → New SSH key |
Füge die Zeile bei Key ein, gib als Titel zum Beispiel Workspace an und speichere.
Die Datei ~/.ssh/id_ed25519 ohne .pub ist dein privater Schlüssel. Den kopierst du nie und lädst ihn nirgendwo hoch.
Teste die Verbindung. Beim ersten Mal fragt SSH nach, ob du dem Server vertraust; antworte mit yes. Danach begrüßt dich der Server mit deinem Benutzernamen:
ssh -T git@git.example.org
Meldet SSH Permission denied (publickey), fehlt der Schlüssel auf dem Server, oder du hast eine andere Zeile kopiert als die aus id_ed25519.pub.
Dein öffentlicher SSH-Schlüssel liegt in ~/.ssh/id_ed25519.pub.
~ ist im Workspace /workspace. ls ~/.ssh zeigt, was dort liegt.
Jeder Commit trägt einen Namen und eine E-Mail-Adresse. Trage deinen echten Namen und die E-Mail-Adresse deines Kontos auf dem Git-Server ein:
git config --global user.name "Vorname Nachname" git config --global user.email "deine.mail@example.org" git config --global pull.rebase false
Die dritte Zeile sagt git pull, was es tun soll, wenn andere inzwischen gepusht haben und du auch schon committet hast: die Commits zusammenführen (merge). Ohne sie bricht git pull dann mit Need to specify how to reconcile divergent branches ab.
Liegt das Repository auf dem Git-Server des Workspace (git.workspace), gibt es dort kein Konto; dann nimm eine Adresse, die du auch anderen zeigen magst, denn sie steht in jedem deiner Commits. Welche Adresse dein Konto auf einem anderen Git-Server hat, steht bei GitLab unter Edit profile → Emails, bei Gitea und Forgejo unter Settings → Account, bei GitHub unter Settings → Emails (wer dort die Adresse privat hält, nimmt die noreply-Adresse, die GitHub anzeigt).
Das ist in jedem Workspace nur einmal nötig. Ohne diese Angaben weigert sich Git zu committen.
Git kennt deinen Namen und eine E-Mail-Adresse.
Klone das Repository der Klasse in deinen Workspace:
git clone [Adresse des Klassen-Repositorys] ~/class-quiz
In ~/class-quiz liegt das Quiz, ein Git-Repository mit einem Remote.
Im Repository liegen:
| Datei | Was darin steht |
|---|---|
quiz.rb, quiz.py
|
das Quiz, in Ruby und in Python |
questions/ |
eine Datei je Runde; bisher nur beispiel.txt
|
rounds.txt |
die Runden, eine pro Zeile, in der Reihenfolge, in der das Quiz sie spielt |
TODO.md |
was am Programm noch fehlt, und wer daran arbeitet |
Sieh dir questions/beispiel.txt an. Eine Frage beginnt mit ?, die Antwort
steht darunter mit =; gelten mehrere Antworten, trennt sie ein |. Zeilen
mit # sind Kommentare.
# Eine Runde zum Ausprobieren ? Wie viele Bits hat ein Byte? = 8 | acht
Leg in questions/ eine Datei für deine Runde an, mit deinem Vornamen klein
und ohne Umlaute, etwa questions/lena.txt, und schreib mindestens drei
Fragen hinein, zu einem Thema, das du gut kennst.
Spiel deine Runde allein, bevor sie ins Quiz kommt:
cd ~/class-quiz
ruby quiz.rb questions/lena.txt
Steht etwas in der Datei, das das Quiz nicht versteht, sagt es, in welcher Zeile.
Trag deine Datei dann in einer neuen Zeile am Ende von rounds.txt ein und
prüf das ganze Quiz:
ruby quiz.rb --check
Spiel deine Runde allein, bevor sie ins Quiz kommt:
cd ~/class-quiz
python3 quiz.py questions/lena.txt
Steht etwas in der Datei, das das Quiz nicht versteht, sagt es, in welcher Zeile.
Trag deine Datei dann in einer neuen Zeile am Ende von rounds.txt ein und
prüf das ganze Quiz:
python3 quiz.py --check
Deine Runde steht in questions/ und am Ende von rounds.txt, und
ruby quiz.rb --check findet nichts mehr. Die Prüfung ruft dafür
problems_in aus quiz.rb auf und zählt die Runden.
Deine Runde steht in questions/ und am Ende von rounds.txt, und
python3 quiz.py --check findet nichts mehr. Die Prüfung ruft dafür
problems_in aus quiz.py auf und zählt die Runden.
Ein Commit sollte nur enthalten, was zu ihm gehört. Darum nimmst du die beiden
Dateien einzeln auf, statt mit git add -A alles, was sich irgendwo geändert
hat:
git add questions/lena.txt rounds.txt git commit -m "Runde von Lena"
Kurz bevor du pushst, hat jemand anderes eine Runde gepusht. Was macht Git mit deinem Push?
Hier geht es weiter, sobald du deine Antwort oben abgegeben hast.
Jetzt pushst du:
git push
Geht der Push durch, war niemand schneller als du; bei den nächsten wird es anders sein. Meist lehnt Git ihn ab:
! [rejected] main -> main (fetch first) error: failed to push some refs to '…' hint: Updates were rejected because the remote contains work that you do not hint: have locally.
Auf dem Server liegt ein Commit, den dein Klon noch nicht kennt. Dein Commit baut auf dem Stand davor auf; nähme der Server ihn einfach an, wäre der andere verloren. Darum holst du zuerst, was fehlt:
git pull --no-edit
--no-edit nimmt für den Merge die Nachricht, die Git vorschlägt, statt dafür
einen Editor zu öffnen.
Du und jemand anderes habt beide eine neue Datei in questions/
angelegt und beide eine Zeile am Ende von rounds.txt angehängt. Wo
meldet git pull einen Konflikt?
Hier geht es weiter, sobald du deine Antwort oben abgegeben hast.
git pull führt die fremden Commits mit deinen zusammen (merge). Neue Dateien
in questions/ sind kein Problem. Aber in rounds.txt habt ihr beide an
derselben Stelle eine Zeile angehängt, und Git weiß nicht, welche zuerst kommen
soll:
CONFLICT (content): Merge conflict in rounds.txt Automatic merge failed; fix conflicts and then commit the result.
In rounds.txt stehen jetzt beide Fassungen, eingerahmt von Markierungen:
questions/beispiel.txt <<<<<<< HEAD questions/lena.txt ======= questions/ida.txt >>>>>>> 04a80e1422d4a1092423ad66fde49833572198e3
Oben steht deine Fassung (HEAD), unten die vom Server mit der Nummer ihres
Commits. Hier sollen beide
Runden ins Quiz, also bleiben beide Zeilen, und die drei Zeilen mit den
Markierungen kommen weg. Erst dann ist der Konflikt gelöst. Ein Programm, das
rounds.txt liest, käme mit den Markierungen nicht zurecht; das Quiz meldet sie
bei --check.
So löst du ihn auf:
rounds.txt, lass beide Zeilen stehen und lösche die Zeilen mit
<<<<<<<, ======= und >>>>>>>. VS Code zeigt über dem Konflikt
»Accept Both Changes«; das tut dasselbe.--check.git add rounds.txt git commit -m "Runden von Lena und Ida" git push
Lehnt Git den Push wieder ab, war in der Zwischenzeit noch jemand schneller:
noch einmal git pull --no-edit, dann git push.
Im Quiz ist nichts mehr offen: Ein Konflikt ist aufgelöst, alles ist committet und gepusht.
Ein Konflikt ist kein Fehler, sondern eine Frage, die Git nicht allein beantworten kann: Welche Fassung soll gelten? Hier war die Antwort leicht. Ändern zwei Leute dieselbe Zeile Code verschieden, müssen sie miteinander reden.
Git lehnt deinen Push ab. Jemand rät dir zu git push --force. Was
passiert dann?
In TODO.md steht, was am Quiz noch fehlt. So eine Liste ist das einfachste
Werkzeug, um Arbeit im Team zu verteilen: Jede und jeder sieht, was offen ist,
wer woran arbeitet und was fertig ist.
Such dir eine Aufgabe aus, die noch niemand hat, und übernimm sie sofort: Hol den neuesten Stand, schreib deinen Namen hinter ihr »Wer:«, und gib das gleich ab, bevor du anfängst. So sehen die anderen, dass sie vergeben ist.
git pull --no-edit git add TODO.md git commit -m "Lena übernimmt: Punkte in Prozent" git push
Wollten zwei dieselbe Aufgabe, gibt es beim zweiten einen Konflikt in
TODO.md. Dann redet ihr miteinander, wer sie macht.
Deine Aufgabe erledigst du in einem eigenen Branch: einem Zweig des
Repositorys, in dem du Commits machen kannst, ohne dass sie main ändern.
Was in main liegt, funktioniert so immer, auch während du arbeitest.
git switch -c prozent
Ändere quiz.rb, probier das Quiz aus, und committe, sobald ein Schritt
funktioniert, gern mehrmals:
ruby quiz.rb git add quiz.rb git commit -m "Punkte am Ende auch in Prozent"
Ändere quiz.py, probier das Quiz aus, und committe, sobald ein Schritt
funktioniert, gern mehrmals:
python3 quiz.py git add quiz.py git commit -m "Punkte am Ende auch in Prozent"
Ist die Aufgabe fertig, holst du den neuesten Stand von main und führst
deinen Branch dort hinein:
git switch main git pull --no-edit git merge --no-edit prozent
Hat jemand anderes dieselben Zeilen geändert, gibt es wieder einen Konflikt,
diesmal im Code. Dann stellst du eine Fassung her, die beides kann, probierst
sie aus und schließt den Merge mit git add und git commit ab. Zum Schluss
verschiebst du deine Zeile in TODO.md unter »Fertig«, committest das und
gibst alles ab:
git push
Du hast eine Aufgabe aus TODO.md übernommen, in einem Branch
erledigt und zusammengeführt. Von dir sind jetzt mindestens drei
Commits im Repository, und alles ist gepusht.
In einem Team liest man die Arbeit der anderen, bevor sie bleibt. Das heißt
Review. git log --oneline zeigt die Commits, git show einen davon mit
allem, was er geändert hat:
git log --oneline
git show a3f9c21
Statt a3f9c21 nimmst du die Nummer eines Commits aus deiner Liste.
Such dir mit git log einen Commit, den jemand anderes für eine Aufgabe
aus TODO.md gemacht hat, und lies ihn mit git show. Wessen Commit
war es, was hat er geändert, und was würdest du genauso oder anders
machen?
Was ihr gerade erlebt habt, gilt in jedem Team, das mit Git arbeitet:
git pull, bevor du anfängst.git add mit Dateinamen statt -A.main liegt nur, was läuft.--force in einem gemeinsamen Repository.