image: teaser.webp image_alt: "Ein Graph von Commits: Von der blauen Linie main zweigen die Branches lena, ida, prozent und tipps in Grün, Orange, Lila und Rot ab und führen wieder in sie zurück." kind: projekt duration: 2 Doppelstunden languages: [ruby, python] curriculum: "Sek I: Projektmanagement (Arbeiten und Kooperieren im Team); Oberstufe: Softwareprojekt (systematische Softwareentwicklung: Versionsverwaltung im Team)" topics: [teamarbeit, softwareprojekt] requires: [git] reference: [git, gitlab] class_repo: group

Im Team mit Git: das Klassenquiz

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.

1. Worum es geht

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.

2. Einrichten

Einmalig: Zugang zum 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.

Aufgabe 1Dein Schlüssel

Dein öffentlicher SSH-Schlüssel liegt in ~/.ssh/id_ed25519.pub.

Tipp

~ 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.

Aufgabe 2Git kennt dich

Git kennt deinen Namen und eine E-Mail-Adresse.

Einmalig: Klonen

Klone das Repository der Klasse in deinen Workspace:

git clone [Adresse des Klassen-Repositorys] ~/class-quiz
Aufgabe 3Das Quiz ist da

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

3. Deine Runde

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
Aufgabe 4Deine Runde

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.

Aufgabe 4Deine Runde

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.

4. Abgeben

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"
Vermutung 5Jemand war schneller

Kurz bevor du pushst, hat jemand anderes eine Runde gepusht. Was macht Git mit deinem Push?

Jemand war schneller

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.

5. Ein Konflikt

Vermutung 6Wo es kracht

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?

Wo es kracht

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:

  1. Öffne 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.
  2. Prüf das Quiz noch einmal mit --check.
  3. Schließ den Merge ab und gib ihn ab:
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.

Aufgabe 7Alles zusammengeführt

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.

Aufgabe 8Mit Gewalt

Git lehnt deinen Push ab. Jemand rät dir zu git push --force. Was passiert dann?

Mit Gewalt

6. Die Arbeit verteilen

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.

Eine Aufgabe übernehmen

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.

In einem eigenen Branch

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
Aufgabe 9Deine Aufgabe ist drin

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.

Einen Commit lesen

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.

Aufgabe 10Ein Commit von jemand anderem

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?

Mindestens 25 Wörter. Nur du und deine Lehrkraft können die Antwort lesen.

7. Regeln fürs Team

Was ihr gerade erlebt habt, gilt in jedem Team, das mit Git arbeitet:

  • Erst holen, dann arbeiten: git pull, bevor du anfängst.
  • Kleine Commits mit klaren Nachrichten: Ein Commit tut eine Sache, und seine Nachricht sagt, welche.
  • Nur aufnehmen, was dazugehört: git add mit Dateinamen statt -A.
  • Abgeben, wenn es funktioniert: In main liegt nur, was läuft.
  • Nie --force in einem gemeinsamen Repository.
  • Reden statt überschreiben: Bei einem Konflikt im Code fragst du die Person, deren Fassung es ist.