Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Versionskontrolle mit GIT

Was ist und was kann GIT?

Git ist ein kostenloses, verteiltes Versionskontrollsystem für Softwareprojekte. Das Programm ermöglicht es mehreren Entwicklern, unabhängig von ihrem Aufenthaltsort gleichzeitig an einem Projekt zu arbeiten.

Die Versionskontrolle macht es einfach, Änderungen am Projekt vorzunehmen, diese Änderungen zu protokollieren und nachzuvollziehen sowie zu einem späteren Zeitpunkt auf ältere Versionen des Projekts zuzugreifen. Git ist plattformunabhängig und lässt sich somit in nahezu jeder Umgebung nutzen.

In folgender Abbildung ist dargestellt, wie drei Entwickler an einem gemeinsamen Softwareprojekt arbeiten könnten. Der Programmcode wird zentral in einem Remote Repository gespeichert. Dieses wird mit lokalen Repositories auf den Rechnern der einzelnen Entwickler synchronisiert. Jedes lokale Repository ist wiederum mit einem Ordner im Dateisystem verbunden, in dem die eigentlichen Projektdateien liegen:

GIT-Repositories

In diesem Abschnitt wollen wir genauer verstehen, wie dieser Workflow funktioniert. Dazu installieren wir zunächst Git auf unserem Computer:

conda install -c conda-forge git

Unter Linux kann Git auch über den Paketmanager installiert werden, da Git nicht nur für Python-Projekte nützlich ist. Unter Ubuntu verwendet man beispielsweise:

sudo apt-get install git

Bei anderen Linux-Distributionen muss entsprechend der jeweilige Paketmanager verwendet werden. Windows-Nutzer können Git von der offiziellen Webseite herunterladen: https://git-scm.com/download/win.

Erste Schritte

Synchronisation mit dem lokalen Repository

Wir wollen uns hier mit den grundlegenden Schritten bei der Arbeit mit Git vertraut machen. Die wichtigsten Befehle, die wir in diesem Kapitel kennenlernen, sind:

BefehlBedeutung
git initLokales Repository initialisieren
git add [files]Dateien zur Versionskontrolle hinzufügen bzw. für den nächsten Commit vormerken
git commit -m "[message]"Vorgemerkte Änderungen in das lokale Repository übernehmen
git logHistorie der Commits anzeigen
git statusStatusbericht anzeigen

Wir legen zunächst einen neuen Ordner für unser Programmierprojekt an und erzeugen uns ein lokales GIT-Repository mit

mkdir my_project
cd my_project
git init
Leeres Git-Repository in /home/user/Documents/my_project/.git/ initialisiert

Dieser Befehl erzeugt einen versteckten Ordner namens .git. In diesem Ordner speichert Git alle Informationen über die Versionsgeschichte des Projekts. In der Regel müssen wir diesen Ordner jedoch nie direkt öffnen.

Wir können nun beginnen, unseren Programmcode zu schreiben. Mit einem Editor unserer Wahl erstellen wir die Datei calender.py und füllen sie mit folgendem Inhalt:

class appointment:
    pass
    
class calender:
    pass

Wir fügen diese zur Versionskontrolle hinzu und speichern sie im lokalen Repository:

git add calender.py
git commit -m "Created file for empty calender and appointment class"
[master (Root-Commit) ce7d6d2] Created empty calender and appointment class
 1 file changed, 6 insertions(+)
 create mode 100644 calender.py

Wir können unser Programm nun erweitern, beispielsweise die appointment-Klasse:

class appointment:
   
    def __init__(self, date, title):
        self.date = date
        self.title = title
    def __str__(self):
        return self.date + ": " + self.title

Mit dem folgenden Befehl können wir überprüfen ob wir noch synchron mit unserem lokalen Repository sind:

git status
Auf Branch master
Änderungen, die nicht zum Commit vorgemerkt sind:
  (benutzen Sie "git add <Datei>...", um die Änderungen zum Commit vorzumerken)
  (benutzen Sie "git restore <Datei>...", um die Änderungen im Arbeitsverzeichnis zu verwerfen)
	geändert:       calender.py

keine Änderungen zum Commit vorgemerkt (benutzen Sie "git add" und/oder "git commit -a")

Wir wollen nun die Änderungen an der Datei calender.py in das lokale Repository hochladen:

git add calender.py
git commit -m "Implemented constructor and string method for appointment class"

Im nächsten Arbeitsschritt erweitern wir unsere calender-Klasse

class calender:
    
    def __init__(self, owner):
        self.owner = owner
        self.appointments = []

    def add_appointment(self, appointment):
        self.appointments.append(appointment)

Anschließend speichern wir Änderungen wieder im lokalen Repository. Wir könnten so vorgehen wie vorhin,

git add calender.py
git commit -m "Implemented constructor and add_appointment method for calender class"

Alternativ - und so wird in der Praxis häufig gearbeitet - können wir den Commit mit nur einer Zeile erstellen:

git commit -am "Implemented constructor and add_appointment method for calender class"

Die Zeile mit git add wurde hierbei weggelassen, dafür wurde beim git add der Parameter -a ergänzt. Der Parameter -a sorgt dafür, dass alle bereits von Git bekannten (also bereits einmal mit git add hinzugefügten) Dateien automatisch für den Commit vorgemerkt werden. Dadurch kann man sich in vielen Fällen den zusätzlichen git add-Befehl sparen.

Nun haben wir bereits 3 Commits erstellt. Wir erhalten eine Historie mit

git log
commit a95560371b9984f57fff4dcbd028bb757a0918cc (HEAD -> master)
Author: Max Winkler <max.winkler@mathematik.tu-chemnitz.de>
Date:   Thu Mar 17 16:04:37 2022 +0100

    Implemented constructor and add_appointment method for calender class

commit ed29a89b272ab66e95cb7d014c90fadccb9cacc1
Author: Max Winkler <max.winkler@mathematik.tu-chemnitz.de>
Date:   Thu Mar 17 16:00:40 2022 +0100

    Implemented constructor and string method for appointment class

commit ce7d6d246e3b0042b70f2b0104ef45139f9de381
Author: Max Winkler <max.winkler@mathematik.tu-chemnitz.de>
Date:   Thu Mar 17 15:50:38 2022 +0100

    Created empty calender class

Wir finden hier unsere Commit-Nachrichten wieder und sehen außerdem, wann die einzelnen Commits erstellt wurden. Außerdem wird jedem Commit eine eindeutige Kennung zugewiesen (der kryptische Code hinter dem Wort “commit”).

Mit einem Remote-Repository verbinden

Wir wollen nun unser lokales Repository mit einem Remote-Repository verbinden. Dies ist insbesondere dann sinnvoll, wenn das Remote-Repository im Internet oder Intranet für andere Entwickler erreichbar ist.

Es gibt mehrere kostenfreie Anbieter für Git-Repositories:

Wir können uns bei einem dieser Anbieter registrieren und dort ein neues Repository anlegen.

Die wichtigsten Befehle, die wir zur Synchronisation mit dem Remote-Repository benötigen, sind:

BefehlBedeutung
git pullÄnderungen aus dem Remote-Repository herunterladen
git pushÄnderungen im lokalen Repository auf das Remote-Repository laden
git remote [...]Verbindung zum Remote-Repository konfigurieren
git clone <url>Klone das Remote-Repository in ein lokales

SSH-Schlüssel erstellen:

Bevor wir mit einem Remote-Repository arbeiten können, müssen wir einen sogenannten SSH-Schlüssel erstellen. Dieser dient zur Authentifizierung beim Zugriff auf das Remote-Repository.

Zunächst überprüfen wir, ob bereits ein öffentlicher SSH-Schlüssel existiert. Dazu geben wir in der Konsole ein:

cat ~/.ssh/id_rsa.pub

Falls die Datei id_rsa.pub im Verzeichnis $HOME/.ssh nicht existiert, müssen wir zunächst ein neues Schlüsselpaar erzeugen:

ssh-keygen -t rsa -b 4096

Nachdem wir den Anweisungen des Programms gefolgt sind, sollten zwei neue Dateien existieren:

~/.ssh/id_rsa      → privater Schlüssel (geheim!)
~/.ssh/id_rsa.pub  → öffentlicher Schlüssel (darf geteilt werden)

Der private Schlüssel darf niemals weitergegeben werden. Der öffentliche Schlüssel hingegen wird benötigt, um uns bei GitLab zu authentifizieren.

Den öffentlichen Schlüssel können wir mit

cat ~/.ssh/id_rsa.pub

anzeigen und anschließend in die Zwischenablage kopieren.

Auf der GitLab-Webseite klicken wir oben rechts auf unseren Avatar, wählen Preferences und navigieren in der linken Seitenleiste zum Menüpunkt SSH Keys. Mit der Schaltfläche Add new key erscheint ein Formular, in das wir unseren öffentlichen Schlüssel einfügen können. Zusätzlich vergeben wir einen Titel (z. B. Max Laptop) und können optional ein Ablaufdatum festlegen.

Dieser Vorgang muss für jedes Gerät durchgeführt werden, von dem aus wir auf das Remote-Repository zugreifen möchten.

Mit dem Remote-Repository verbinden:

Beim ersten Versuch unseren Code zu “pushen” wird uns eine Warnung angezeigt:

git push
fatal: Kein Ziel für "push" konfiguriert.
Entweder spezifizieren Sie die URL von der Befehlszeile oder konfigurieren ein Remote-Repository unter Benutzung von

    git remote add <Name> <URL>

und führen "push" dann unter Benutzung dieses Namens aus

    git push <Name>

Dies ist nicht verwunderlich, da wir Git noch nicht mitgeteilt haben, wo sich unser Remote-Repository befindet. Wie wir dies tun können, steht bereits im Fehlertext.

Die URL unseres Remote-Repositories finden wir beispielsweise im GitLab-Webinterface, wenn wir den Button Clone anklicken:

Remote-Repository

Dabei verwenden wir in der Regel die SSH-URL. Mit dieser können wir unser lokales Repository wie folgt mit dem Remote-Repository verknüpfen:

git remote add Gitlab git@gitlab.hrz.tu-chemnitz.de:maxwin--tu-chemnitz.de/python-lecture.git
git push Gitlab
Objekte aufzählen: 9, fertig.
Zähle Objekte: 100% (9/9), fertig.
Delta-Kompression verwendet bis zu 4 Threads.
Komprimiere Objekte: 100% (6/6), fertig.
Schreibe Objekte: 100% (9/9), 983 Bytes | 327.00 KiB/s, fertig.
Gesamt 9 (Delta 1), Wiederverwendet 0 (Delta 0), Pack wiederverwendet 0
To gitlab.hrz.tu-chemnitz.de:maxwin--tu-chemnitz.de/python-lecture.git
 * [new branch]      master -> master

Auf der GitLab-Webseite unseres Projekts sollte nun auch die Datei calender.py erscheinen.

Beachte dabei, dass die Weboberfläche nur den Inhalt des Remote-Repositories anzeigt. Änderungen, die sich nur in unserem lokalen Repository befinden und noch nicht mit git push übertragen wurden, sind dort nicht sichtbar.

Ist das Remote-Repository erst einmal eingerichtet, können andere Programmierer dieses herunterladen und mit der Arbeit am Projekt beginnen. Dazu verwendet man den Befehl

git clone git@gitlab.hrz.tu-chemnitz.de:maxwin--tu-chemnitz.de/python-lecture.git

Dieser Befehl erstellt ein neues lokales Repository und lädt gleichzeitig alle Dateien sowie die gesamte Versionshistorie des Projekts herunter.

Anschließend darf der Programmierer Dateien verändern, die Änderungen in sein lokales Repository committen und mit

git push

in das Remote-Repository übertragen. Ein weiterer Programmierer kann sich diese Änderungen wiederum mit

git pull

in sein lokales Repository laden.

Mit dem Befehl

git remote -v

kann man sich jederzeit anzeigen lassen, mit welchen Remote-Repositories das lokale Repository verbunden ist. Dabei werden sowohl die Adresse zum Herunterladen (fetch) als auch zum Hochladen (push) der Daten angezeigt.

Im Team arbeiten

Merges und Mergekonflikte

Wir haben bereits gelernt, wie wir die Daten unseres lokalen Repositories mit dem Remote-Repository synchronisieren können (push und pull). Weitere Programmierer können dieses Repository ebenfalls klonen (clone) und uns bei der Programmierung unterstützen.

Doch was passiert eigentlich, wenn mehrere Benutzer gleichzeitig Änderungen einpflegen?

Wir testen dies aus und lassen unser Repository von zwei Programmierern – Programmierer A und Programmierer B – klonen. Diese arbeiten nun unabhängig voneinander an der calender- bzw. an der appointment-Klasse weiter:

Programmierer A

from datetime import datetime

class appointment:

    def __init__(self, date, title):
        try:
            self.date = datetime.strptime(date, '%d.%m.%y %H:%M:%S')
        except:
            print("Fehler:", date, "ist kein gültiges Datumsformat.")

        self.title = title
    
    def __str__(self):
        return str(self.date) + ": " + self.title
        
    def __lt__(self, other):
        return self.date <= self.other

Programmierer B

class calender:

    def __init__(self, owner):
        self.owner = owner
        self.appointments = []

    def add_appointment(self, appointment):
        self.appointments.append(appointment)

    def __str__(self):

        res = "Calender of "+self.owner+":\n"
        
        if len(res) == 0:
            print("<keine Einträge vorhanden>")
        else:
            for appointment in self.appointments:
                res += str(appointment) + "\n"
        return res

Beide Programmierer können während ihrer Arbeit beliebig mit git add und git commit ihren Programmcode mit ihren lokalen Repositories synchronisieren. Kritisch wird es allerdings beim git push.

Die Programmiererin, die zuerst ihre Änderungen in das Remote-Repository lädt (git push), kann dies ohne Konflikte tun. Der zweite Programmierer erhält jedoch folgende Fehlermeldung:

git push
To gitlab.hrz.tu-chemnitz.de:maxwin--tu-chemnitz.de/python-lecture.git
 ! [rejected]        master -> master (fetch first)
error: failed to push some refs to 'git@gitlab.hrz.tu-chemnitz.de:maxwin--tu-chemnitz.de/python-lecture.git'
hint: Updates were rejected because the remote contains work that you do
hint: not have locally. This is usually caused by another repository pushing
hint: to the same ref. You may want to first integrate the remote changes
hint: (e.g., 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Durch die Änderungen von Programmierer A ist also ein Konflikt mit den lokalen Daten von Programmierer B entstanden. Programmierer B muss zunächst diese Änderungen herunterladen (git pull) und eventuell entstandene Konflikte im Programmcode beheben, bevor er seine Änderungen wieder in das Remote-Repository übertragen kann.

Gibt Programmierer B nun

git pull

ein, versucht Git automatisch, die Änderungen beider Programmierer zusammenzuführen (Merge). Dieser Merge ist selbst wieder ein Commit. Daher wird man nach einer Commit-Nachricht gefragt. Es öffnet sich ein Texteditor mit folgendem Inhalt:

Merge branch 'master' of gitlab.hrz.tu-chemnitz.de:maxwin--tu-chemnitz.de/python-lecture

Diese Nachricht kann man einfach übernehmen und den Editor anschließend speichern und schließen (beispielsweise in Vim mit :wq).

Im Idealfall erscheint anschließend eine Erfolgsmeldung auf der Konsole:

remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From gitlab.hrz.tu-chemnitz.de:maxwin--tu-chemnitz.de/python-lecture
   a955603..5872b6e  master     -> origin/master
Auto-merging calender.py
Merge made by the 'recursive' strategy.
 calender.py | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

Dies bestätigt, dass der Merge erfolgreich war. Öffnet man nun die Datei calender.py, sieht man, dass die Änderungen beider Programmierer enthalten sind.

Programmierer B kann nun anschließend mit

git push

die aktuellste Version wieder in das Remote-Repository übertragen.

Wir überprüfen nun mit git log, versehen mit einigen Zusatzoptionen, was eigentlich passiert ist:

git log --oneline --graph
*   224c7c9 (HEAD -> master, origin/master, origin/HEAD) 
    Merge branch 'master' of gitlab.hrz.tu-chemnitz.de:maxwin--tu-chemnitz.de/python-lecture
|\  
| * 5872b6e Modified appointment class
* | 209c906 Modified calender class
|/  
* a955603 Implemented constructor and add_appointment method for calender class
* ed29a89 Implemented constructor and string method for appointment class
* ce7d6d2 Created empty calender class

Links erkennt man eine Baumstruktur. Nach dem dritten Commit verzweigt sich die Historie in zwei Zweige (Branches), jeweils für die Commits der beiden Programmierer, die zu diesem Zeitpunkt nicht mehr synchron waren. Beim git pull hat Programmierer B diese beiden Zweige wieder zusammengeführt.

Das war der einfache Fall. Was passiert jedoch, wenn der automatische Merge fehlschlägt? Beispielsweise dann, wenn beide Programmierer dieselbe Zeile verändern.

Nehmen wir an, beide Programmierer fügen einen Kommentar zur Klasse appointment hinzu:

Programmierer A

# Class that stores date and title of an appointment
class appointment:

Programmierer B

# Class represents an appointment of the calender owner
class appointment:

Beide committen und pushen ihre Änderungen. Beim zweiten Programmierer schlägt der git push fehl und er muss zunächst mit git pull die Änderungen herunterladen:

git pull
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 1), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From gitlab.hrz.tu-chemnitz.de:maxwin--tu-chemnitz.de/python-lecture
   224c7c9..c7bebfa  master     -> origin/master
Auto-merging calender.py
CONFLICT (content): Merge conflict in calender.py
Automatic merge failed; fix conflicts and then commit the result.

Der Merge ist offensichtlich fehlgeschlagen. Git kann schließlich nicht entscheiden, welche Version korrekt ist. Programmierer B muss daher die Datei calender.py im Editor öffnen und findet dort folgende Markierungen:

<<<<<<< HEAD
# Class represents an appointment of the calender owner
=======
# Class that stores date and title of an appointment
>>>>>>> c7bebfacf5aa664e1f3ed705794856828970b787
class appointment:

Diese Markierungen zeigen den Konfliktbereich an. Programmierer B muss nun den Konflikt manuell auflösen, also entscheiden, welche Version erhalten bleiben soll. Anschließend müssen die Konfliktmarkierungen entfernt werden.

Danach kann er die Änderungen mit

git commit -am "Solved merge conflict"
git push

wieder in das lokale und anschließend in das Remote-Repository übertragen.

Programmiererin A erhält diese Änderungen beim nächsten git pull ebenfalls.

Arbeiten mit Branches

Um Konflikte zwischen mehreren Programmierern zu reduzieren, lohnt es sich, für neue Features eigene Entwicklungszweige (Branches) anzulegen und gezielt auf diesen zu arbeiten.

Ist ein Feature fertig implementiert, kann es anschließend in den Haupt-Branch (meist main, früher häufig master) integriert werden.

GIT-Branches

Wir lernen hier folgende Befehle kennen:

BefehlBedeutung
git branch ...Branch erzeugen/löschen/verwalten
git checkout <branch>Den Branch wechseln
git merge <branch>Branch mit dem aktuellen verschmelzen

Einen neuen Branch erstellen

Wir stellen uns nun vor, dass Programmiererin A und B getrennt voneinander weiter arbeiten. Während Programmiererin A weiter auf dem master-Branch arbeitet und vielleicht ein Paar Testskripte für die Verwendung unserer calender-Klasse programmiert, arbeitet Programmierer B an neuen Features für unseren Kalender. Um die Arbeit von Programmiererin A nicht zu stören erzeugt Programmierer B einen neuen Branch:

git branch calender_features
git checkout calender_features
Switched to branch 'calender_features'

Alternativ kann man einen neuen Branch auch direkt erstellen und zu diesem wechseln mit

git checkout -b calender_features

Wir können uns mit

git branch
  master
* calender_features

noch einmal die verfügbaren Branches anzeigen lassen. Der Stern markiert dabei den Branch, auf dem wir uns aktuell befinden.

Wir können nun die Klasse calender erweitern. Beispielsweise fügen wir folgende Methode hinzu:

    def remove_old_appointments(self):
        today = datetime.today()
        upcoming_appointments = []

        for appointment in self.appointments:
            if appointment.date > today:
                upcoming_appointments.append(appointment)

        self.appointments = upcoming_appointments

Wir schauen uns noch einmal die Ausgabe von git status an:

git status
On branch calender_features
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

	modified:   calender.py

no changes added to commit (use "git add" and/or "git commit -a")

Die erste Zeile zeigt uns, dass wir uns tatsächlich auf dem Branch calender_features befinden.

Wir können nun committen und anschließend versuchen zu pushen:

git commit -am "Implemented method to remove old appointments"
git push
fatal: The current branch calender_features has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin calender_features

Der Grund ist, dass der neue Branch bisher nur lokal existiert. Das Remote-Repository kennt diesen Branch noch nicht. Beim ersten Push müssen wir daher angeben, dass ein neuer Remote-Branch erstellt werden soll.

Dies erledigen wir mit:

git push --set-upstream origin calender_features
Counting objects: 3, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 484 bytes | 161.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: 
remote: To create a merge request for calender_features, visit:
remote:   https://gitlab.hrz.tu-chemnitz.de/maxwin--tu-chemnitz.de/python-lecture/-/merge_requests/new?merge_request%5Bsource_branch%5D=calender_features
remote: 
To gitlab.hrz.tu-chemnitz.de:maxwin--tu-chemnitz.de/python-lecture.git
 * [new branch]      calender_features -> calender_features
Branch 'calender_features' set up to track remote branch 'calender_features' from 'origin'.

Auf der GitLab-Webseite sehen wir in der Rubrik Branches nun ebenfalls diesen neuen Branch.

Mit git log können wir überprüfen, auf welchem Branch wir uns lokal und remote befinden:

git log
commit 2671386641522f6d2ceeffdec51263830f76d86d 
    (HEAD -> calender_features, origin/calender_features)

Der HEAD ist ein Zeiger auf den aktuell ausgecheckten Commit, also die Spitze des Branches, auf dem wir gerade arbeiten. In unserem Fall zeigt HEAD auf den Branch calender_features, welcher mit dem Remote-Branch origin/calender_features verknüpft ist.

Der Name origin bezeichnet dabei einfach unser Remote-Repository. Wir können uns die konfigurierten Remote-Repositories mit folgendem Befehl anzeigen lassen:

git remote -v
origin	git@gitlab.hrz.tu-chemnitz.de:maxwin--tu-chemnitz.de/python-lecture.git (fetch)
origin	git@gitlab.hrz.tu-chemnitz.de:maxwin--tu-chemnitz.de/python-lecture.git (push)

Was hat Programmiererin A in der Zwischenzeit gemacht? Sie war weiterhin auf dem master-Branch unterwegs und hat ein kleines Testskript test.py geschrieben, dieses committed und anschließend gepusht. Da beide Entwickler auf unterschiedlichen Branches gearbeitet haben, gab es beim Pushen keine Konflikte.

Branches mergen

Programmierer B hat mittlerweile seine Arbeit beendet, sein neues Feature ausgiebig getestet und möchte dieses nun in den master-Branch integrieren.

Dazu wechselt er zunächst auf den master-Branch:

git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.

Anschließend lädt er mit

git pull

die Änderungen herunter, die andere Programmiererinnen in der Zwischenzeit durchgeführt haben. Dieser Schritt ist wichtig, damit wir nicht mit einer veralteten Version des master-Branches arbeiten.

Nun kann Programmierer B (oder auch Programmiererin A) den Merge durchführen:

git merge calender_features

Es öffnet sich ein Editor, in dem eine Commit-Nachricht für den Merge eingetragen werden kann. Nach dem Speichern erhalten wir beispielsweise folgende Ausgabe:

Merge made by the 'recursive' strategy.
 calender.py | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)

Der automatische Merge war hier erfolgreich. Es kann jedoch vorkommen, dass Git einen Konflikt nicht automatisch auflösen kann, beispielsweise wenn auf beiden Branches dieselben Codezeilen verändert wurden. In diesem Fall müssen die Konflikte manuell im Quellcode behoben werden.

Zum Abschluss laden wir den neuen Stand noch in das Remote-Repository:

git push

Anschließend kann ganz normal auf dem master-Branch weitergearbeitet werden. Beispielsweise könnte Programmierer B sein neues Feature in das Testskript test.py einbauen.

Branch schließen

Nach dem erfolgreichen Merge wird der Branch calender_features nicht mehr benötigt. Dabei werden nicht die Commits gelöscht, sondern lediglich der Zeiger auf diesen Branch entfernt. Die Änderungen bleiben weiterhin Teil der Projekthistorie.

Lokal können wir den Branch mit

git branch -d calender_features
Deleted branch calender_features (was 2671386).

löschen.

Der Befehl git branch zeigt uns diesen Branch nun nicht mehr an. Auf dem Remote-Repository existiert er jedoch weiterhin, was wir auch auf der GitLab-Webseite sehen können.

Um den Branch auch dort zu löschen verwenden wir:

git push --delete origin calender_features
To gitlab.hrz.tu-chemnitz.de:maxwin--tu-chemnitz.de/python-lecture.git
 - [deleted]         calender_features

Zusammenfassung

Wir können uns nun noch einmal die Commit-Historie anschauen:

git log --oneline --graph
* 79912a3 (HEAD -> master, origin/master, origin/HEAD) Extended test script test.py
*   e501a9e Merge branch 'calender_features'
|\  
| * 2671386 Implemented method to remove old appointments
* | 17a8414 Implemented test script
|/  
*   b53b4d2 Resolved merge conflict
|\  
| * c7bebfa Added comment to appointment class
* | 4e73b1c Added comment to appointment class
|/  
*   224c7c9 Merge branch 'master' of gitlab.hrz.tu-chemnitz.de:maxwin--tu-chemnitz.de/python-lecture
|\  
| * 5872b6e Modified appointment class
* | 209c906 Modified calender class
|/  
* a955603 Implemented constructor and add_appointment method for calender class
* ed29a89 Implemented constructor and string method for appointment class
* ce7d6d2 Created empty calender class

Links erkennen wir eine Baumstruktur. Diese zeigt, wie sich verschiedene Entwicklungszweige (Branches) im Laufe der Zeit verzweigt und später wieder zusammengeführt haben.

Auch auf dem Remote-Repository ergibt sich ein ähnliches Bild. Unter Repository → Graph sehen wir folgenden Graphen:

GIT-Repositories

Hier sehen wir sowohl die Branches, die durch gleichzeitiges Arbeiten auf dem master-Branch entstanden sind, als auch unseren manuell erzeugten calender_features-Branch.

Git-Cheatsheet (kompakt)

BefehlBedeutung / Tipp
git initLokales Repository erstellen
git clone <url>Repository vom Remote herunterladen
git statusZeigt Änderungen, Branch und Commit-Status
git add <datei>Datei zum nächsten Commit vormerken
git commit -m "Nachricht"Änderungen festschreiben
git commit -am "Nachricht"Änderungen in bekannten Dateien add + commit in einem Schritt
git pullÄnderungen vom Remote einholen (immer zuerst!)
git pushEigene Änderungen in das Remote-Repository hochladen
git branchAlle Branches anzeigen
git checkout <branch>Auf einen anderen Branch wechseln
git checkout -b <branch>Branch erstellen und direkt wechseln
git merge <branch>Anderen Branch in den aktuellen integrieren
git log --oneline --graphHistorie kompakt als Baum anzeigen
git remote -vZeigt die Remote-Repository URLs an
git push --set-upstream origin <branch>Neuen lokalen Branch mit Remote verknüpfen
git push --delete origin <branch>Remote-Branch löschen

Tipp für Teams:

  • Vor jedem Push immer git pull ausführen, um Konflikte zu vermeiden.

  • Für neue Features eigene Branches nutzen und nach Fertigstellung mergen.

  • Konflikte rechtzeitig manuell lösen, nicht einfach überschreiben.

  • SSH-Key einmal erstellen, dann kann jeder Push/Pull ohne Passwort funktionieren.

Was gehört in ein Repository?

Ein Git-Repository sollte in der Regel nur Quellcode und wichtige Projektdateien enthalten. Dateien, die automatisch erzeugt werden oder leicht neu erstellt werden können, gehören normalerweise nicht in ein Repository.

Typischerweise gehören in ein Repository:

  • Quellcode (z.B. .py, .c, .cpp)

  • Konfigurationsdateien

  • Dokumentation (.md, .tex)

  • Skripte und Build-Dateien

  • kleine Beispieldaten

Folgende Dateien sollten dagegen nicht versioniert werden:

  • kompilierte Programme (.exe, .out, .class)

  • automatisch erzeugte Dateien (.log, .aux, .toc)

  • erzeugte PDF-Dateien aus LaTeX

  • Python-Cache-Dateien (__pycache__/, .pyc)

  • temporäre Dateien oder Editor-Backups

  • große Datenmengen

Solche Dateien werden normalerweise in einer Datei namens .gitignore aufgeführt. Git ignoriert alle dort eingetragenen Dateien und nimmt sie nicht in Commits auf.

Ein einfaches Beispiel für eine .gitignore-Datei in einem Python-Projekt wäre:

__pycache__/
*.pyc
*.log
*.aux
*.toc
*.out
*.pdf

Diese Datei wird im Hauptverzeichnis des Repositories gespeichert und ebenfalls versioniert, damit alle Entwickler die gleichen Regeln verwenden.