8. Versionsverwaltung mit Git
Versionsverwaltung mit Git
Abschnitt betitelt „Versionsverwaltung mit Git“Wer länger an einem Projekt arbeitet, kennt das Problem der Versionsstände: project.py, project_old.py, project_final.py, project_final_v2.py, project_final_v2_FIXED.py. Niemand weiß mehr, welche Datei welchen Stand enthält, was sich zwischen den Ständen geändert hat und welche Fassung eigentlich funktioniert hat. In Teamarbeit verschärft sich das Problem: Zwei Personen ändern dieselbe Datei, und beim Zusammenkopieren geht Arbeit verloren.
Versionsverwaltung löst genau das. Ein Versionsverwaltungssystem protokolliert die Geschichte eines Projekts als Folge von nachvollziehbaren Ständen, erlaubt die Rückkehr zu jedem früheren Stand und führt die Arbeit mehrerer Personen kontrolliert zusammen. Das mit Abstand am weitesten verbreitete System ist Git, entwickelt 2005 von Linus Torvalds für die Arbeit am Linux-Kernel. Git ist heute in der Softwareentwicklung so grundlegend wie ein Editor; auch außerhalb der Programmierung wird es eingesetzt, etwa für Dokumentation, Konfiguration oder wissenschaftliche Arbeiten.
Die Konzepte
Abschnitt betitelt „Die Konzepte“Bevor Kommandos ins Spiel kommen, die Begriffe. Sie gelten für jedes Versionsverwaltungssystem und sind ausdrücklich Prüfungsstoff.
- Repository: die Datenbank mit der gesamten Geschichte eines Projekts. Bei Git liegt sie in einem versteckten Ordner
.gitdirekt im Projektverzeichnis; das Projekt trägt seine Geschichte also vollständig mit sich. - Commit: ein dauerhaft gespeicherter Schnappschuss des gesamten Projekts zu einem Zeitpunkt, zusammen mit Autor, Datum, einer Beschreibung und einem Verweis auf den Vorgänger-Commit. Jeder Commit hat eine eindeutige ID (einen 40-stelligen Hash wie
a3f8e21..., von dem im Alltag die ersten sieben Zeichen genügen). - Historie: die Kette der Commits, jeder mit Verweis auf seinen Vorgänger. Sie beantwortet die Fragen: Was wurde geändert? Wann? Von wem? Und, bei guten Beschreibungen: Warum?
- Branch: eine benannte, unabhängige Entwicklungslinie. Branches erlauben es, an einer neuen Funktion zu arbeiten, ohne den funktionierenden Hauptstand anzutasten.
- Merge: das Zusammenführen zweier Entwicklungslinien zu einem gemeinsamen Stand.
- Remote: ein entferntes Repository auf einem Server, über das Teammitglieder ihre Stände austauschen.
Die drei Bereiche von Git
Abschnitt betitelt „Die drei Bereiche von Git“Git unterscheidet drei Bereiche, zwischen denen Änderungen wandern. Dieses Modell zu verstehen erspart die meiste Verwirrung im Umgang mit den Kommandos:
flowchart LR
wd["Working Directory<br>(die Dateien, die Sie editieren)"]
st["Staging Area<br>(die Auswahl für den nächsten Commit)"]
repo["Repository<br>(die gespeicherte Historie)"]
wd -- "git add" --> st
st -- "git commit" --> repo
repo -- "git restore / checkout" --> wd
- Das Working Directory sind die gewöhnlichen Projektdateien, an denen gearbeitet wird.
- Die Staging Area ist ein Zwischenschritt: Mit
git addwird ausgewählt, welche Änderungen in den nächsten Commit aufgenommen werden. Das erlaubt es, von fünf geänderten Dateien nur die drei zusammengehörigen zu committen und den Rest für einen späteren, thematisch eigenen Commit aufzuheben. - Das Repository enthält die committeten, dauerhaften Stände.
Einrichtung
Abschnitt betitelt „Einrichtung“Git wird von git-scm.com installiert (unter Linux über den Paketmanager, unter Windows als “Git for Windows” inklusive Git Bash). Einmalig werden Name und E-Mail-Adresse hinterlegt; beide landen in jedem Commit:
git config --global user.name "Anna Muster"Der tägliche Zyklus
Abschnitt betitelt „Der tägliche Zyklus“Ein neues Repository entsteht mit git init im Projektordner. Danach besteht die tägliche Arbeit aus einem Kreislauf von vier Kommandos:
git status # what has changed?git add timecode.py # stage the changegit commit -m "Add input validation for negative frame numbers"git log --oneline # show the historyEin Beispieldurchlauf mit den zugehörigen Ausgaben:
$ git statusOn branch mainChanges not staged for commit: modified: timecode.pyUntracked files: notes.txt
$ git add timecode.py$ git commit -m "Add input validation for negative frame numbers"[main 4f2a9c1] Add input validation for negative frame numbers 1 file changed, 4 insertions(+)
$ git log --oneline4f2a9c1 Add input validation for negative frame numbersb81d3e0 Extract timecode conversion into a functiona3f8e21 Initial version of the timecode toolgit status ist das wichtigste Kommando von allen: Es zeigt jederzeit, welche Dateien geändert, welche gestaged und welche unbekannt sind, und schlägt sogar die passenden nächsten Kommandos vor. Bei Unsicherheit gilt immer: zuerst git status.
Zwei weitere Kommandos vervollständigen den Alltag:
git diffzeigt zeilengenau, was sich seit dem letzten Commit geändert hat;git diff --stagedzeigt, was der nächste Commit enthalten wird. Vor jedem Commit einen Blick auf das Diff zu werfen ist eine Gewohnheit, die versehentliche Commits von Debug-Ausgaben und Experimenten verhindert.git restore <file>verwirft nicht committete Änderungen einer Datei und stellt den letzten committeten Stand her. Achtung: Die verworfenen Änderungen sind damit tatsächlich weg.
Was ist ein guter Commit?
Abschnitt betitelt „Was ist ein guter Commit?“Die Historie ist nur so nützlich wie ihre Commits. Zwei Regeln:
- Ein Commit, ein Thema. Ein Commit fasst eine zusammengehörige Änderung: eine Funktion, eine Fehlerbehebung, eine Umbenennung. Ein Commit “alles von heute” mit zwölf durchmischten Änderungen ist bei der Fehlersuche wertlos, denn er lässt sich weder verstehen noch gezielt zurücknehmen.
- Die Message beschreibt die Änderung. Bewährte Konvention: eine kurze Zeile im Imperativ, die den Satz “Dieser Commit wird …” vervollständigt.
Add input validation for negative frame numberserfüllt das;changes,fixoderasdfnicht. Wer in drei Monaten pergit lognach der Ursache eines Fehlers sucht, liest ausschließlich diese Zeilen.
.gitignore: Was nicht in die Historie gehört
Abschnitt betitelt „.gitignore: Was nicht in die Historie gehört“Nicht alles im Projektordner gehört unter Versionskontrolle. Automatisch erzeugte Dateien (__pycache__/, virtuelle Umgebungen, Renderergebnisse, Exportdateien) werden in einer Datei .gitignore im Projektwurzelverzeichnis aufgezählt und von Git fortan ignoriert:
__pycache__/.venv/*.mp4exports/Grundregel: Ins Repository gehört, was Menschen geschrieben haben (Quellcode, Konfiguration, Dokumentation); was daraus erzeugt werden kann, bleibt draußen. Gerade in Medienprojekten ist das relevant: Git ist für Textdateien gebaut. Große Binärdateien wie Videomaterial blähen das Repository dauerhaft auf, denn Git behält jede jemals committete Version. Rohmaterial und Renderergebnisse werden daher außerhalb verwaltet; versioniert wird das Projekt, nicht das Material.
Branches: parallele Entwicklungslinien
Abschnitt betitelt „Branches: parallele Entwicklungslinien“Der Hauptbranch (üblicherweise main) soll jederzeit einen funktionierenden Stand enthalten. Neue Funktionen, Experimente und größere Umbauten finden auf eigenen Branches statt und kommen erst nach Fertigstellung zurück:
git branch feature-export # create a new branchgit switch feature-export # start working on it# ... edit, add, commit as usual ...git switch main # back to the main linegit merge feature-export # bring the finished work into maingit branch -d feature-export # delete the merged branchgitGraph
commit id: "a3f8e21"
commit id: "b81d3e0"
branch feature-export
commit id: "c92f1a4"
commit id: "d15e8b7"
checkout main
commit id: "e77c203"
merge feature-export
Ein Branch ist bei Git nur ein beweglicher Zeiger auf einen Commit; das Anlegen kostet nichts und dauert keine Sekunde. Entsprechend niedrig soll die Hemmschwelle sein: Jede abgegrenzte Aufgabe bekommt ihren Branch. Der Wert zeigt sich spätestens dann, wenn mitten in einer halbfertigen Funktion ein dringender Fehler im Hauptstand zu beheben ist: git switch main, Fehler beheben, committen, zurück auf den Feature-Branch, weiterarbeiten. Ohne Branches müsste die halbfertige Arbeit erst irgendwie beiseitegeräumt werden.
git merge führt die Linien zusammen. Haben sich beide Seiten seit der Abzweigung geändert, erzeugt Git einen Merge-Commit mit zwei Vorgängern (wie im Diagramm oben); die Historie wird damit zu einem Graphen statt einer Kette.
Merge-Konflikte: Normalfall, keine Katastrophe
Abschnitt betitelt „Merge-Konflikte: Normalfall, keine Katastrophe“Ändern zwei Branches dieselben Zeilen derselben Datei, kann Git nicht wissen, welche Fassung gelten soll, und meldet einen Konflikt:
Auto-merging timecode.pyCONFLICT (content): Merge conflict in timecode.pyAutomatic merge failed; fix conflicts and then commit the result.Wichtig vorweg: Es ist nichts kaputt und nichts verloren. Git hat lediglich die Entscheidung an den Menschen zurückgegeben und markiert die strittigen Stellen in der Datei:
def frames_to_timecode(frames, fps=25):<<<<<<< HEAD if frames < 0: raise ValueError("frame number must be >= 0")======= if frames < 0: frames = 0>>>>>>> feature-export total_seconds = frames // fpsZwischen <<<<<<< HEAD und ======= steht die Fassung des aktuellen Branches, darunter bis >>>>>>> die des gemergten Branches. Die Auflösung ist Handarbeit mit drei Schritten:
-
Entscheiden. Die Datei öffnen und pro Konfliktstelle festlegen, was gelten soll: die eine Fassung, die andere oder eine Kombination. Das ist eine inhaltliche Entscheidung; im Beispiel oben stehen zwei unvereinbare Fehlerstrategien gegeneinander, und ein Werkzeug kann diese Entscheidung prinzipiell nicht treffen. VS Code zeigt Konflikte mit klickbaren Auswahlknöpfen an, die Entscheidung selbst bleibt beim Menschen.
-
Marker entfernen. Alle Zeilen mit
<<<<<<<,=======und>>>>>>>müssen verschwinden; übrig bleibt die gewünschte, lauffähige Fassung. -
Testen, dann abschließen. Das Programm ausführen, dann
git add timecode.pyundgit commit. Der Merge ist damit abgeschlossen.
Konflikte sind kein Zeichen für Fehler, sondern die logische Folge paralleler Arbeit. Sie bleiben klein, wenn Branches kurzlebig sind und regelmäßig gemergt wird; gefürchtet werden nur Konflikte nach wochenlanger getrennter Entwicklung.
Remote-Repositories: Zusammenarbeit über den Server
Abschnitt betitelt „Remote-Repositories: Zusammenarbeit über den Server“Bisher lag alles lokal. Für Abgaben, Backups und Teamarbeit kommt ein Remote dazu: ein Repository auf einem Server, im Unterricht auf dem GitLab der Schule. Die Verbindung läuft über drei Kommandos:
git clone <url>holt ein bestehendes Repository vom Server, inklusive der gesamten Historie. Damit beginnt die Arbeit an jedem Übungsprojekt.git pushüberträgt die eigenen neuen Commits zum Server.git pullholt neue Commits vom Server in das lokale Repository.
Der Arbeitstag in Teamarbeit folgt damit einem festen Rhythmus: mit git pull den aktuellen Stand holen, lokal arbeiten und committen, mit git push die Ergebnisse teilen. Wer vor dem Push von einem Kollegen überholt wurde, muss zuerst pullen; haben beide dieselben Zeilen geändert, entsteht dabei ein gewöhnlicher Merge-Konflikt, der wie oben aufgelöst wird.
Für die Übungsabgaben gilt ab jetzt der Git-Workflow: Jede Übung ist ein Repository auf dem Schul-GitLab, abgegeben wird durch Push, und die Commit-Historie ist Teil der Abgabe. Eine Abgabe, die aus einem einzigen Commit “final” um 23:58 Uhr besteht, erzählt eine andere Geschichte als eine mit zwölf nachvollziehbaren Schritten über eine Woche.
Der Nutzen im Rückblick
Abschnitt betitelt „Der Nutzen im Rückblick“Mit dem kompletten Werkzeugkasten lassen sich die eingangs genannten Probleme abhaken. Die Dateinamens-Versionierung (final_v2_FIXED) ersetzt die Historie: git log zeigt alle Stände, git diff a3f8e21 4f2a9c1 zeigt die Änderungen zwischen zwei beliebigen Ständen, und git switch --detach a3f8e21 stellt jeden früheren Stand vollständig wieder her. Verlorene Arbeit durch Zusammenkopieren ersetzt der Merge mit expliziter Konfliktauflösung. Und die Frage “Wer hat das geändert, und warum?” beantwortet git log <file> samt Commit-Messages, pro Datei und auf Wunsch pro Zeile (git blame).
Versionsverwaltung ist damit kein Zusatzwerkzeug für große Teams, sondern die Grundlage nachvollziehbaren Arbeitens, auch allein. Ab diesem Kapitel wird jedes Projekt dieses Schuljahres in einem Git-Repository geführt, einschließlich des Abschlussprojekts.
Kommandoübersicht
Abschnitt betitelt „Kommandoübersicht“| Kommando | Zweck |
|---|---|
git init | neues Repository im aktuellen Ordner anlegen |
git status | Zustand von Working Directory und Staging zeigen |
git add <file> | Änderung für den nächsten Commit vormerken |
git commit -m "..." | vorgemerkte Änderungen dauerhaft speichern |
git log --oneline | Historie kompakt anzeigen |
git diff / git diff --staged | Änderungen zeilengenau anzeigen |
git restore <file> | lokale Änderungen verwerfen |
git branch <name> / git switch <name> | Branch anlegen / wechseln |
git merge <name> | Branch in den aktuellen Branch zusammenführen |
git clone <url> | Repository vom Server holen |
git pull / git push | Commits vom/zum Server übertragen |
Lernergebnisse: Was Sie nach diesem Kapitel können sollten
Abschnitt betitelt „Lernergebnisse: Was Sie nach diesem Kapitel können sollten“Nach Abschluss dieses Kapitels sollten Schülerinnen und Schüler in der Lage sein:
- Erklären: die Konzepte der Versionsverwaltung (Repository, Commit, Historie, Branch, Merge, Remote) mit eigenen Worten erklären.
- Erklären: das Drei-Bereiche-Modell von Git (Working Directory, Staging Area, Repository) und den Weg einer Änderung hindurch beschreiben.
- Anwenden: den täglichen Zyklus aus
status,add,commit,logunddiffsicher anwenden und aussagekräftige Commit-Messages schreiben. - Anwenden: Branches anlegen, zwischen ihnen wechseln, mergen und Merge-Konflikte kontrolliert auflösen.
- Anwenden: mit Remote-Repositories arbeiten (
clone,push,pull) und den Abgabe-Workflow über das Schul-GitLab einhalten. - Beurteilen: entscheiden, welche Dateien unter Versionskontrolle gehören und welche per
.gitignoreausgeschlossen werden.
Passende Übungen
Abschnitt betitelt „Passende Übungen“- Aufgabe 15 - Git I: Zeitmaschine
- Aufgabe 16 - Git II: Abzweigungen