image: git-logo.png image_alt: "Das Logo von Git" kind: grundlagen duration: 3 Doppelstunden curriculum: "Sek I: Projektmanagement (Arbeiten und Kooperieren im Team); Oberstufe: Softwareprojekt (systematische Softwareentwicklung)" topics: [teamarbeit] reference: [workspace, terminal, git, gitlab]

Versionsverwaltung mit Git

Git ist ein Versionsverwaltungssystem, das von Linus Torvalds entwickelt wurde, also ein Werkzeug, mit dem Änderungen an Dateien über längere Zeit hinweg nachvollziehbar gespeichert werden können. Es wird zur Verwaltung von Quellcode in Softwareprojekten eingesetzt und gehört heute zu den bekanntesten und am weitesten verbreiteten Werkzeugen dieser Art. Der eigentliche Nutzen von Git zeigt sich jedoch erst, wenn man selbst an größeren Texten oder Programmen arbeitet und dabei immer wieder Änderungen vornimmt. Oft möchte man etwas ausprobieren, eine Idee testen oder einen Fehler beheben – und merkt später, dass es doch nicht die richtige Lösung war. Ohne ein Werkzeug wie Git ist es dann schwierig, zu einem früheren, funktionierenden Stand zurückzukehren oder überhaupt noch nachzuvollziehen, was sich wann geändert hat.

Linus Torvalds 2018 auf einer Konferenz: ein Mann mit Brille und kurzem Haar in einem schwarzen T-Shirt, lächelnd.

Linus Torvalds (geboren 1969). Das Terminal im Workspace läuft unter Linux: Das hat er 1991 als Student in Helsinki angefangen, »nur ein Hobby«, schrieb er damals. Heute läuft Linux auf den meisten Servern der Welt und in jedem Android-Handy.

2005 brauchte er für die Arbeit an Linux eine neue Versionsverwaltung und schrieb in wenigen Tagen die erste Fassung von Git.

Foto: Raysonho, 2018, CC0 (Wikimedia Commons)

Git löst dieses Problem, indem es die Entwicklung eines Projekts als eine Abfolge von nachvollziehbaren Zwischenschritten speichert. Diese Zwischenschritte bilden eine Art Geschichte des Projekts, zu der man jederzeit zurückkehren kann. Auf diese Weise wird es möglich, Änderungen zu vergleichen, alternative Lösungswege auszuprobieren und Fehler rückgängig zu machen, ohne Angst haben zu müssen, etwas dauerhaft zu zerstören.

In diesem Tutorial lernst du Git Schritt für Schritt kennen. Wir beginnen ganz lokal auf deinem eigenen Rechner und schauen uns zunächst an, wie Git Änderungen speichert und organisiert. Erst später werden wir darauf eingehen, wie Git auch die Zusammenarbeit mehrerer Personen an einem gemeinsamen Projekt unterstützt.

1. Vorbereitung

Stelle zuerst sicher, dass du keinen Ordner geöffnet hast. Um sicherzugehen, drücke einfach den Shortcut für »Ordner schließen«: StrgK und dann F. Dein Workspace sollte jetzt ungefähr so aussehen:

Ein leerer Workspace ohne geöffneten Ordner.

Schließe die linke Seitenleiste mit StrgB, um mehr Platz zu haben, und öffne das Terminal mit StrgJ:

Der Workspace mit geöffnetem Terminal im unteren Bereich.

Rechts oben im Terminal das Symbol zum Maximieren, der Mauszeiger darauf, darunter der Hinweis »Maximize Panel«.

Mit dem Symbol rechts oben im Terminal kannst du es maximieren. Die linke Seitenleiste blendest du jederzeit mit StrgB ein und aus.

Erstelle als erstes ein Verzeichnis für dieses Tutorial, indem du im Terminal folgenden Befehl eingibst:

mkdir git-tutorial

Mit ls -l kannst du anschließend überprüfen, ob das Verzeichnis erstellt wurde (das d am Anfang der Zeile zeigt an, dass es sich um ein Verzeichnis handelt):

Im Terminal legt mkdir git-tutorial das Verzeichnis an; ls -l zeigt es mit einem d am Anfang der Zeile.

Falls du mehr über die Grundlagen der Kommandozeile erfahren möchtest, schau dir das Tutorial zu Dateien und Verzeichnissen an.

Wechsle anschließend in das neu erstellte Verzeichnis und lege ein Verzeichnis namens alice für eine fiktive Person Alice an:

cd git-tutorial
mkdir alice
cd alice

2. Alleine arbeiten

Ein Repository anlegen

Initialisiere jetzt ein neues Git-Repository mit dem Befehl git init:

cd git-tutorial, mkdir alice, cd alice und git init, das »Initialized empty Git repository in /workspace/git-tutorial/alice/.git/« meldet.

Mit dem Befehl ls -la kannst du sehen, dass ein verstecktes Verzeichnis namens .git erstellt wurde:

ls -la zeigt im Verzeichnis alice das versteckte Verzeichnis .git.

Das .git-Verzeichnis enthält alle Informationen, die Git benötigt, um die Versionsgeschichte deines Projekts zu verwalten. Darin befindet sich später die gesamte Historie deiner Änderungen.

Das Vorhandensein eines .git-Verzeichnisses zeigt an, dass sich das aktuelle Verzeichnis in einem Git-Repository befindet. Wenn du ein Verzeichnis löschst, das ein .git-Verzeichnis enthält, verlierst du die gesamte Versionsgeschichte dieses Repositories und hast »nur noch« ein normales Verzeichnis ohne Versionskontrolle.

Die erste Datei

Erstelle jetzt eine Datei namens hello.txt mit dem Befehl echo:

echo "Hello, world!" > hello.txt

Überzeuge dich mit dem Befehl cat hello.txt, dass die Datei den erwarteten Inhalt hat:

echo "Hello, world!" > hello.txt legt die Datei an, cat hello.txt gibt Hello, world! aus.

Der Befehl echo gibt den angegebenen Text im Terminal aus. Mit dem Zeichen > leitest du die Ausgabe in eine Datei um. Wenn die Datei noch nicht existiert, wird sie erstellt. Wenn sie bereits existiert, wird ihr Inhalt überschrieben. Der Befehl cat zeigt den Inhalt einer Datei im Terminal an.

Verwende nun den Befehl git status, um den aktuellen Status des Repositories zu überprüfen:

git status meldet On branch main, No commits yet und hello.txt in Rot unter Untracked files.

Die Staging Area

Die Ausgabe von git status zeigt, dass die Datei hello.txt als »untracked« (nicht verfolgt) markiert ist. Das bedeutet, dass Git diese Datei noch nicht in die Versionskontrolle aufgenommen hat. Bei Git werden Änderungen nicht direkt zum Repository hinzugefügt, sondern zunächst in eine sogenannte »Staging Area« (auch »Index« genannt) kopiert. Verwende den Befehl git add, um die Datei zur Staging Area hinzuzufügen:

git add hello.txt
Durch git add sagen wir Git explizit, dass wir die Datei hello.txt in die nächste Commit-Änderung aufnehmen möchten. Dies gibt uns die Möglichkeit, Änderungen selektiv zu committen, anstatt alle Änderungen auf einmal zu übernehmen.

Überprüfe den Status erneut mit git status:

Nach git add hello.txt meldet git status hello.txt in Grün als new file unter Changes to be committed.

Die Ausgabe zeigt jetzt, dass die Datei hello.txt in der Staging Area bereitsteht, um committet zu werden.

Aufgabe 1Was macht git add?

Du hast hello.txt geändert und git add hello.txt ausgeführt. Wo steht die Änderung jetzt?

Was macht git add?

Einen Commit erstellen

Um die Änderungen dauerhaft im Repository zu speichern, verwende den Befehl git commit. Füge die Option -m hinzu, um eine Commit-Nachricht anzugeben, die beschreibt, was du geändert hast (falls du die Nachricht weglässt, öffnet Git einen Texteditor, in dem du die Nachricht eingeben kannst):

git commit -m "initial commit"

Die Ausgabe zeigt, dass ein neuer Commit erstellt wurde:

git commit -m "initial commit" meldet einen root-commit auf main, 1 file changed, 1 insertion, create mode 100644 hello.txt.

Ab hier zeigt die Seite nach jedem Schritt, wie Git seine Daten im Verzeichnis .git speichert. Für die Arbeit mit Git genügt davon wenig: Jeder Commit ist ein Stand aller Dateien und kennt den Commit davor, und main und HEAD zeigen auf Commits. Blobs, Trees und ihre IDs sind zum Vertiefen da. Keine Aufgabe fragt nach ihnen, und beim ersten Lesen kannst du die Absätze über sie überspringen.

Im .git-Verzeichnis ist jetzt ein gerichteter, azyklischer Graph (DAG) entstanden, der mehrere Knoten enthält, wobei jeder Knoten durch eine eindeutige SHA-1-Hash-ID identifiziert wird. Diese Knoten repräsentieren die verschiedenen Objekte in Git: Blobs, Trees und Commits.

git_objects <!-- bb2150aa043e460938c2cd2a21201ea906a689c1 --> bb2150aa043e460938c2cd2a21201ea906a689c1 commit bb2150a tree ec947e3   author [...]                 initial commit <!-- ec947e3dd7a7752d078f1ed0cfde7457b21fef58 --> ec947e3dd7a7752d078f1ed0cfde7457b21fef58 tree ec947e3 blob hello.txt (af5626b) <!-- bb2150aa043e460938c2cd2a21201ea906a689c1->ec947e3dd7a7752d078f1ed0cfde7457b21fef58 --> bb2150aa043e460938c2cd2a21201ea906a689c1->ec947e3dd7a7752d078f1ed0cfde7457b21fef58 tree <!-- af5626b4a114abcb82d63db7c8082c3c4756e51b --> af5626b4a114abcb82d63db7c8082c3c4756e51b blob af5626b Hello, world! <!-- ec947e3dd7a7752d078f1ed0cfde7457b21fef58->af5626b4a114abcb82d63db7c8082c3c4756e51b --> ec947e3dd7a7752d078f1ed0cfde7457b21fef58->af5626b4a114abcb82d63db7c8082c3c4756e51b hello.txt <!-- ref:main --> ref:main main <!-- ref:main->bb2150aa043e460938c2cd2a21201ea906a689c1 --> ref:main->bb2150aa043e460938c2cd2a21201ea906a689c1 <!-- ref:HEAD --> ref:HEAD HEAD <!-- ref:HEAD->ref:main --> ref:HEAD->ref:main

  • Blob: Der Blob-Knoten mit der SHA1-ID af5626b repräsentiert den Inhalt »Hello, world!« (der Dateiname spielt dabei noch keine Rolle).
  • Tree: Der Tree-Knoten mit der SHA1-ID ec947e3 repräsentiert ein Verzeichnis, das die Datei hello.txt enthält, deren Inhalt durch den Blob af5626b repräsentiert wird.
  • Commit: Der Commit-Knoten repräsentiert einen Snapshot des Repositories zu einem bestimmten Zeitpunkt. Er verweist auf den Tree ec947e3, der den Zustand des Verzeichnisses zum Zeitpunkt des Commits beschreibt. Der Commit enthält auch Metadaten wie den Autor, das Datum und die Commit-Nachricht.
Blobs und Trees haben bei dir dieselben IDs wie hier, denn ihre ID hängt nur vom Inhalt ab. Die IDs deiner Commits sind andere: In einen Commit fließen auch dein Name und die Uhrzeit ein.

Zusätzlich gibt es noch zwei Zeiger:

  • main: Der Zeiger main zeigt auf den neuesten Commit im main-Branch.
  • HEAD: Der Zeiger HEAD zeigt auf den aktuellen Branch (main in diesem Fall). Der daraus resultierende Commit ist der aktuelle Zustand des Repositories, also die Dateien, die sich gerade im Arbeitsverzeichnis befinden.

Die Ausgabe von git status zeigt außerdem an, dass wir keine lokalen Änderungen haben, die noch nicht committet wurden und unser Arbeitsverzeichnis »sauber« ist:

git status meldet On branch main, nothing to commit, working tree clean.

Aufgabe 2Der erste Commit

Lege in git-tutorial/alice ein Repository an und committe hello.txt.

Ausprobieren: Git zeigt dir seine Objekte auch selbst.
  • git cat-file -p HEAD zeigt deinen Commit so, wie Git ihn gespeichert hat. Welche ID steht hinter tree? Gib sie an git cat-file -p weiter, und danach die ID, die im Tree steht: Was kommt jeweils heraus?
  • git hash-object hello.txt rechnet die ID einer Datei aus, ohne etwas zu speichern. Stimmt sie mit dem Blob oben überein? Dann echo "Hello, World!" | git hash-object --stdin, mit großem W: Wie viel von der ID ändert sich durch einen einzigen Buchstaben?

Dateien umbenennen

Benenne die Datei hello.txt in README.md um, indem du den Befehl git mv verwendest:

git mv hello.txt README.md
git mv ist eine Abkürzung: Es benennt die Datei um wie mv und legt die Änderung gleich in die Staging Area. Mit mv hello.txt README.md und danach git add -A kommt genau derselbe Commit heraus. Git speichert nämlich gar keine Umbenennungen, sondern nur, welche Dateien mit welchem Inhalt es in einem Commit gibt. Dass README.md die alte hello.txt ist, erkennt Git erst beim Anzeigen am gleichen Inhalt.

Erzeuge anschließend einen neuen Commit:

git commit -m "rename hello.txt to README.md"

Die Ausgabe zeigt, dass ein neuer Commit erstellt wurde. Die 100% hinter der Umbenennung sind die Ähnlichkeit, an der Git sie erkannt hat: Der Inhalt ist derselbe geblieben.

Nach git mv hello.txt README.md und git commit meldet Git den neuen Commit mit »1 file changed« und »rename hello.txt => README.md (100%)«.'></p>

<p>Der Git-Objektgraph sieht jetzt folgendermaßen aus:</p>

<!-- tutorial-screenshot
git-graph: git-tutorial/alice
-->

<p><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0.00 0.00 928.00 228.50" class="full">
<g id="graph0" class="graph git-graph" transform="scale(1 1) rotate(0) translate(4 224.5)">
<title>git_objects</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-224.5 924,-224.5 924,4 -4,4"/>
<!-- ccfdafa28c04051e0b95f45afec59e259d27d05c -->
<g id="node1" class="node commit current">
<title>ccfdafa28c04051e0b95f45afec59e259d27d05c</title>
<polygon fill="#5bc0b5" stroke="transparent" points="182,-120.5 182,-220.5 428,-220.5 428,-120.5 182,-120.5"/>
<text text-anchor="start" x="185" y="-207.3" font-family="monospace" font-weight="bold" font-size="14.00" fill="#000000">commit ccfdafa</text>
<text text-anchor="start" x="185" y="-187.3" font-family="monospace" font-size="14.00" fill="#000000">tree ebfd749                 </text>
<text text-anchor="start" x="185" y="-172.3" font-family="monospace" font-size="14.00" fill="#000000">parent bb2150a               </text>
<text text-anchor="start" x="185" y="-157.3" font-family="monospace" font-size="14.00" fill="#000000">author [...]                 </text>
<text text-anchor="start" x="185" y="-142.3" font-family="monospace" font-size="14.00" fill="#000000">                             </text>
<text text-anchor="start" x="185" y="-127.3" font-family="monospace" font-size="14.00" fill="#000000">rename hello.txt to README.md</text>
<polygon fill="none" stroke="#000000" points="182,-120.5 182,-220.5 428,-220.5 428,-120.5 182,-120.5"/>
</g>
<!-- ebfd7499dae526dcbaf30e1250b1f875946729f8 -->
<g id="node2" class="node tree current">
<title>ebfd7499dae526dcbaf30e1250b1f875946729f8</title>
<polygon fill="#d8c59f" stroke="transparent" points="485,-141.5 485,-181.5 690,-181.5 690,-141.5 485,-141.5"/>
<text text-anchor="start" x="488.5" y="-168.3" font-family="monospace" font-weight="bold" font-size="14.00" fill="#000000">tree ebfd749</text>
<text text-anchor="start" x="488.5" y="-148.3" font-family="monospace" font-size="14.00" fill="#000000">blob README.md (af5626b)</text>
<polygon fill="none" stroke="#000000" points="485,-141.5 485,-181.5 690,-181.5 690,-141.5 485,-141.5"/>
</g>
<!-- ccfdafa28c04051e0b95f45afec59e259d27d05c->ebfd7499dae526dcbaf30e1250b1f875946729f8 -->
<g id="edge1" class="edge current">
<title>ccfdafa28c04051e0b95f45afec59e259d27d05c->ebfd7499dae526dcbaf30e1250b1f875946729f8</title>
<path fill="none" stroke="#000000" d="M428.22,-166.58C443.57,-166.09 459.22,-165.58 474.4,-165.1"/>
<polygon fill="#000000" stroke="#000000" points="474.95,-168.58 484.84,-164.76 474.73,-161.58 474.95,-168.58"/>
<text text-anchor="middle" x="456.5" y="-169.3" font-family="Times,serif" font-size="14.00">tree</text>
</g>
<!-- bb2150aa043e460938c2cd2a21201ea906a689c1 -->
<g id="node3" class="node commit faded">
<title>bb2150aa043e460938c2cd2a21201ea906a689c1</title>
<polygon fill="#ceece9" stroke="transparent" points="244,0 244,-85 366,-85 366,0 244,0"/>
<text text-anchor="start" x="247" y="-71.3" font-family="monospace" font-weight="bold" font-size="14.00" fill="#808080">commit bb2150a</text>
<text text-anchor="start" x="247" y="-51.3" font-family="monospace" font-size="14.00" fill="#808080">tree ec947e3  </text>
<text text-anchor="start" x="247" y="-36.3" font-family="monospace" font-size="14.00" fill="#808080">author [...]  </text>
<text text-anchor="start" x="247" y="-21.3" font-family="monospace" font-size="14.00" fill="#808080">              </text>
<text text-anchor="start" x="247" y="-6.3" font-family="monospace" font-size="14.00" fill="#808080">initial commit</text>
<polygon fill="none" stroke="#b3b3b3" points="244,0 244,-85 366,-85 366,0 244,0"/>
</g>
<!-- ccfdafa28c04051e0b95f45afec59e259d27d05c->bb2150aa043e460938c2cd2a21201ea906a689c1 -->
<g id="edge2" class="edge faded">
<title>ccfdafa28c04051e0b95f45afec59e259d27d05c->bb2150aa043e460938c2cd2a21201ea906a689c1</title>
<path fill="none" stroke="#b3b3b3" d="M305,-120.07C305,-111.94 305,-103.5 305,-95.32"/>
<polygon fill="#b3b3b3" stroke="#b3b3b3" points="308.5,-95.12 305,-85.12 301.5,-95.12 308.5,-95.12"/>
<text text-anchor="middle" x="294.5" y="-99.05" font-family="Times,serif" font-size="14.00">parent</text>
</g>
<!-- af5626b4a114abcb82d63db7c8082c3c4756e51b -->
<g id="node4" class="node blob current">
<title>af5626b4a114abcb82d63db7c8082c3c4756e51b</title>
<polygon fill="#e7e6e1" stroke="transparent" points="806,-103.5 806,-143.5 920,-143.5 920,-103.5 806,-103.5"/>
<text text-anchor="start" x="809" y="-130.3" font-family="monospace" font-weight="bold" font-size="14.00" fill="#000000">blob af5626b</text>
<text text-anchor="start" x="809" y="-110.3" font-family="monospace" font-size="14.00" fill="#000000">Hello, world!</text>
<polygon fill="none" stroke="#000000" points="806,-103.5 806,-143.5 920,-143.5 920,-103.5 806,-103.5"/>
</g>
<!-- ebfd7499dae526dcbaf30e1250b1f875946729f8->af5626b4a114abcb82d63db7c8082c3c4756e51b -->
<g id="edge3" class="edge current">
<title>ebfd7499dae526dcbaf30e1250b1f875946729f8->af5626b4a114abcb82d63db7c8082c3c4756e51b</title>
<path fill="none" stroke="#000000" d="M690.34,-147.35C725.56,-142.46 764.09,-137.1 795.71,-132.71"/>
<polygon fill="#000000" stroke="#000000" points="796.3,-136.16 805.72,-131.32 795.33,-129.23 796.3,-136.16"/>
<text text-anchor="middle" x="748" y="-148.3" font-family="Times,serif" font-size="14.00">README.md</text>
</g>
<!-- ec947e3dd7a7752d078f1ed0cfde7457b21fef58 -->
<g id="node5" class="node tree faded">
<title>ec947e3dd7a7752d078f1ed0cfde7457b21fef58</title>
<polygon fill="#f3eee2" stroke="transparent" points="485,-74.5 485,-114.5 690,-114.5 690,-74.5 485,-74.5"/>
<text text-anchor="start" x="488.5" y="-101.3" font-family="monospace" font-weight="bold" font-size="14.00" fill="#808080">tree ec947e3</text>
<text text-anchor="start" x="488.5" y="-81.3" font-family="monospace" font-size="14.00" fill="#808080">blob hello.txt (af5626b)</text>
<polygon fill="none" stroke="#b3b3b3" points="485,-74.5 485,-114.5 690,-114.5 690,-74.5 485,-74.5"/>
</g>
<!-- bb2150aa043e460938c2cd2a21201ea906a689c1->ec947e3dd7a7752d078f1ed0cfde7457b21fef58 -->
<g id="edge4" class="edge faded">
<title>bb2150aa043e460938c2cd2a21201ea906a689c1->ec947e3dd7a7752d078f1ed0cfde7457b21fef58</title>
<path fill="none" stroke="#b3b3b3" d="M366.17,-53.66C397.87,-59.53 437.77,-66.93 474.88,-73.81"/>
<polygon fill="#b3b3b3" stroke="#b3b3b3" points="474.29,-77.26 484.76,-75.64 475.56,-70.37 474.29,-77.26"/>
<text text-anchor="middle" x="456.5" y="-75.3" font-family="Times,serif" font-size="14.00">tree</text>
</g>
<!-- ec947e3dd7a7752d078f1ed0cfde7457b21fef58->af5626b4a114abcb82d63db7c8082c3c4756e51b -->
<g id="edge5" class="edge faded">
<title>ec947e3dd7a7752d078f1ed0cfde7457b21fef58->af5626b4a114abcb82d63db7c8082c3c4756e51b</title>
<path fill="none" stroke="#b3b3b3" d="M690.18,-100.8C721.61,-103.25 756.29,-106.45 788,-110.5 790.55,-110.83 793.14,-111.18 795.76,-111.55"/>
<polygon fill="#b3b3b3" stroke="#b3b3b3" points="795.51,-115.05 805.92,-113.08 796.55,-108.13 795.51,-115.05"/>
<text text-anchor="middle" x="748" y="-114.3" font-family="Times,serif" font-size="14.00">hello.txt</text>
</g>
<!-- ref:main -->
<g id="node6" class="node ref">
<title>ref:main</title>
<polygon fill="white" stroke="black" points="139,-188.5 91,-188.5 91,-152.5 145,-152.5 145,-182.5 139,-188.5"/>
<polyline fill="none" stroke="black" points="139,-188.5 139,-182.5 "/>
<polyline fill="none" stroke="black" points="145,-182.5 139,-182.5 "/>
<text text-anchor="middle" x="118" y="-166.8" font-family="monospace" font-size="14.00">main</text>
</g>
<!-- ref:main->ccfdafa28c04051e0b95f45afec59e259d27d05c -->
<g id="edge6" class="edge ref">
<title>ref:main->ccfdafa28c04051e0b95f45afec59e259d27d05c</title>
<path fill="none" stroke="black" d="M145.11,-170.5C152.88,-170.5 161.97,-170.5 171.8,-170.5"/>
<polygon fill="black" stroke="black" points="171.85,-174 181.85,-170.5 171.85,-167 171.85,-174"/>
</g>
<!-- ref:HEAD -->
<g id="node7" class="node ref head">
<title>ref:HEAD</title>
<polygon fill="#fce94f" stroke="black" points="48,-188.5 0,-188.5 0,-152.5 54,-152.5 54,-182.5 48,-188.5"/>
<polyline fill="none" stroke="black" points="48,-188.5 48,-182.5 "/>
<polyline fill="none" stroke="black" points="54,-182.5 48,-182.5 "/>
<text text-anchor="middle" x="27" y="-166.8" font-family="monospace" font-size="14.00">HEAD</text>
</g>
<!-- ref:HEAD->ref:main -->
<g id="edge7" class="edge ref">
<title>ref:HEAD->ref:main</title>
<path fill="none" stroke="black" d="M54.22,-170.5C62.55,-170.5 71.91,-170.5 80.82,-170.5"/>
<polygon fill="black" stroke="black" points="80.97,-174 90.97,-170.5 80.97,-167 80.97,-174"/>
</g>
</g>
</svg></p>

<p>Wie du siehst, sind die alten Objekte (Blob <code>af5626b</code> und Tree <code>ec947e3</code>) weiterhin vorhanden, da Git die gesamte Versionsgeschichte beibehält. Da die Datei jetzt einen neuen Namen hat, wurde ein neuer Tree-Knoten <code>ebfd749</code> erstellt, der die Datei <code>README.md</code> enthält, die auf den gleichen Blob <code>af5626b</code> verweist. Da sich der Inhalt der Datei nicht geändert hat, bleibt der Blob unverändert. Zusätzlich wurde ein neuer Commit-Knoten erstellt, der auf den neuen Tree <code>ebfd749</code> verweist und den vorherigen Commit als Vorgänger hat.</p>

<h3>Dateien ändern</h3>

<p>Öffne jetzt das Verzeichnis <code>/workspace/git-tutorial/alice</code> in VS Code: Drücke <kbd>Strg</kbd><kbd>K</kbd> und dann <kbd>Strg</kbd><kbd>O</kbd>, wähle das Verzeichnis aus und klicke auf »OK«:</p>

<!-- tutorial-screenshot
terminal-run: clear
press: Control+K
press: Control+O
wait-for-input-value: /workspace/
type: git-tutorial/alice/
wait-for-input-value: /workspace/git-tutorial/alice/
sleep: 0.5
crop-bottom: 75%
-->

<p><img class=

Öffne die Datei README.md, ändere world zu planet und füge eine neue Zeile hinzu, sodass in der Datei Folgendes steht:

Hello, planet!

Git is awesome!

Im Ordner alice ist README.md offen, mit »Hello, planet!« und »Git is awesome!«; im Explorer ist die Datei mit M als geändert markiert.

Speichere die Datei mit StrgS und überprüfe den Status mit git status im Terminal:

git status zeigt README.md in Rot als modified unter Changes not staged for commit.

Überprüfe vor jedem Commit, welche Änderungen du vorgenommen hast, indem du den Befehl git diff ausführst:

git diff zeigt für README.md die Zeile »Hello, world!« in Rot entfernt und »Hello, planet!«, eine Leerzeile und »Git is awesome!« in Grün hinzugefügt.

Du siehst jetzt die aktuellen Änderungen in deinem Arbeitsverzeichnis. Rote Zeilen (-) zeigen entfernte Zeilen an, während grüne Zeilen (+) hinzugefügte Zeilen darstellen.

Wenn du einen Commit erstellen möchtest, der alle Änderungen in deinem Arbeitsverzeichnis umfasst, kannst du den Befehl git commit mit der Option -a verwenden. Diese Option fügt automatisch alle geänderten und gelöschten Dateien zur Staging Area hinzu, bevor der Commit erstellt wird (du sparst dir so git add). Führe den folgenden Befehl aus, um die Änderungen zu committen:

git commit -a -m "updated README.md"
Die Option -a bei git commit ist praktisch, wenn du alle Änderungen in deinem Arbeitsverzeichnis auf einmal committen möchtest. Beachte jedoch, dass diese Option nur für bereits verfolgte Dateien gilt. Neue, untracked Dateien müssen weiterhin mit git add zur Staging Area hinzugefügt werden, bevor sie committet werden können.

Der Git-Objektgraph sieht jetzt folgendermaßen aus:

git_objects <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc --> fa88648dfe0b66e674835fe10bbee36116cfa9bc commit fa88648 tree 130f7a9      parent ccfdafa    author [...]                       updated README.md <!-- 130f7a90b97d88faa2f1ddf794ef7dace1c0c450 --> 130f7a90b97d88faa2f1ddf794ef7dace1c0c450 tree 130f7a9 blob README.md (1bce388) <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc->130f7a90b97d88faa2f1ddf794ef7dace1c0c450 --> fa88648dfe0b66e674835fe10bbee36116cfa9bc->130f7a90b97d88faa2f1ddf794ef7dace1c0c450 tree <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c --> ccfdafa28c04051e0b95f45afec59e259d27d05c commit ccfdafa tree ebfd749                  parent bb2150a                author [...]                                               rename hello.txt to README.md <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc->ccfdafa28c04051e0b95f45afec59e259d27d05c --> fa88648dfe0b66e674835fe10bbee36116cfa9bc->ccfdafa28c04051e0b95f45afec59e259d27d05c parent <!-- 1bce3880336548b24d6aadcc51e3d41359c5908d --> 1bce3880336548b24d6aadcc51e3d41359c5908d blob 1bce388 Hello, planet!                Git is awesome! <!-- 130f7a90b97d88faa2f1ddf794ef7dace1c0c450->1bce3880336548b24d6aadcc51e3d41359c5908d --> 130f7a90b97d88faa2f1ddf794ef7dace1c0c450->1bce3880336548b24d6aadcc51e3d41359c5908d README.md <!-- ebfd7499dae526dcbaf30e1250b1f875946729f8 --> ebfd7499dae526dcbaf30e1250b1f875946729f8 tree ebfd749 blob README.md (af5626b) <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c->ebfd7499dae526dcbaf30e1250b1f875946729f8 --> ccfdafa28c04051e0b95f45afec59e259d27d05c->ebfd7499dae526dcbaf30e1250b1f875946729f8 tree <!-- bb2150aa043e460938c2cd2a21201ea906a689c1 --> bb2150aa043e460938c2cd2a21201ea906a689c1 commit bb2150a tree ec947e3   author [...]                 initial commit <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c->bb2150aa043e460938c2cd2a21201ea906a689c1 --> ccfdafa28c04051e0b95f45afec59e259d27d05c->bb2150aa043e460938c2cd2a21201ea906a689c1 parent <!-- af5626b4a114abcb82d63db7c8082c3c4756e51b --> af5626b4a114abcb82d63db7c8082c3c4756e51b blob af5626b Hello, world! <!-- ebfd7499dae526dcbaf30e1250b1f875946729f8->af5626b4a114abcb82d63db7c8082c3c4756e51b --> ebfd7499dae526dcbaf30e1250b1f875946729f8->af5626b4a114abcb82d63db7c8082c3c4756e51b README.md <!-- ec947e3dd7a7752d078f1ed0cfde7457b21fef58 --> ec947e3dd7a7752d078f1ed0cfde7457b21fef58 tree ec947e3 blob hello.txt (af5626b) <!-- bb2150aa043e460938c2cd2a21201ea906a689c1->ec947e3dd7a7752d078f1ed0cfde7457b21fef58 --> bb2150aa043e460938c2cd2a21201ea906a689c1->ec947e3dd7a7752d078f1ed0cfde7457b21fef58 tree <!-- ec947e3dd7a7752d078f1ed0cfde7457b21fef58->af5626b4a114abcb82d63db7c8082c3c4756e51b --> ec947e3dd7a7752d078f1ed0cfde7457b21fef58->af5626b4a114abcb82d63db7c8082c3c4756e51b hello.txt <!-- ref:main --> ref:main main <!-- ref:main->fa88648dfe0b66e674835fe10bbee36116cfa9bc --> ref:main->fa88648dfe0b66e674835fe10bbee36116cfa9bc <!-- ref:HEAD --> ref:HEAD HEAD <!-- ref:HEAD->ref:main --> ref:HEAD->ref:main

Wie du sehen kannst, zeigen der main-Branch und HEAD jetzt auf den neuesten Commit, der die Datei README.md mit dem aktualisierten Inhalt enthält. Da der Inhalt verändert wurde, musste ein neuer Blob angelegt werden. Die alten Knoten bleiben weiterhin im Graphen erhalten.

Aufgabe 3Umbenannt und geändert

Benenne hello.txt in README.md um, ändere die Datei und committe beides, wie oben beschrieben.

Zeitreise

Lass uns jetzt zu einem früheren Commit zurückkehren. Verwende den Befehl git log, um die Commit-Historie anzuzeigen:

git log zeigt drei Commits mit Autor und Datum, »updated README.md« mit HEAD -> main, »rename hello.txt to README.md« und »initial commit«.

Du siehst eine Liste der Commits, beginnend mit dem neuesten Commit. Passt sie nicht ins Terminal, zeigt Git sie seitenweise, und unten steht ein Doppelpunkt: Mit den Pfeiltasten oder der Leertaste blätterst du weiter, mit q kommst du zurück zur Eingabe. Du siehst außerdem, dass der main-Branch auf den neuesten Commit zeigt und gerade ausgecheckt ist, da auch HEAD auf diesen Commit zeigt.

Was heißt »ausgecheckt«? In Git bedeutet »auschecken«, dass du den Zustand des Repositories zu einem bestimmten Commit wiederherstellst. Wenn du einen Commit auscheckst, ändert Git die Dateien in deinem Arbeitsverzeichnis so, dass sie dem Zustand entsprechen, der in diesem Commit gespeichert ist. Standardmäßig zeigt HEAD auf den neuesten Commit im aktuellen Branch. Wenn du jedoch einen älteren Commit auscheckst, verschiebst du HEAD auf diesen älteren Commit, und dein Arbeitsverzeichnis wird entsprechend aktualisiert.
Vermutung 4HEAD~2

Was zeigt dein Arbeitsverzeichnis nach git checkout HEAD~2?

HEAD~2

Wir checken nun eine frühere Version des Repositories aus, nämlich die Version HEAD~2, also zwei Commits vor dem aktuellen HEAD:

git checkout HEAD~2

Hier geht es weiter, sobald du deine Antwort oben abgegeben hast.

Nach git checkout HEAD~2 erklärt Git den Zustand »detached HEAD« und meldet am Ende HEAD is now at … initial commit.

Nachdem du einen älteren Commit ausgecheckt hast, befindest du dich in einem sogenannten »detached HEAD«-Zustand. In diesem Zustand zeigt HEAD nicht auf einen Branch, sondern direkt auf einen bestimmten Commit. Das heißt, dass es momentan keinen Branch gibt, der auf diesen Commit zeigt. Änderungen, die du in diesem Zustand vornimmst, werden daher auch nicht automatisch einem Branch zugeordnet. Wenn du später zu einem Branch zurückkehrst, gehen diese Änderungen verloren, sofern du sie nicht vorher in einem neuen Branch speicherst oder du dir die Commit-ID notierst.

Du siehst auch, dass die Datei README.md jetzt wieder hello.txt heißt und den ursprünglichen Inhalt »Hello, world!« hat:

ls zeigt nur hello.txt, cat hello.txt gibt Hello, world! aus.

Der Git-Objektgraph sieht jetzt folgendermaßen aus:

git_objects <!-- bb2150aa043e460938c2cd2a21201ea906a689c1 --> bb2150aa043e460938c2cd2a21201ea906a689c1 commit bb2150a tree ec947e3   author [...]                 initial commit <!-- ec947e3dd7a7752d078f1ed0cfde7457b21fef58 --> ec947e3dd7a7752d078f1ed0cfde7457b21fef58 tree ec947e3 blob hello.txt (af5626b) <!-- bb2150aa043e460938c2cd2a21201ea906a689c1->ec947e3dd7a7752d078f1ed0cfde7457b21fef58 --> bb2150aa043e460938c2cd2a21201ea906a689c1->ec947e3dd7a7752d078f1ed0cfde7457b21fef58 tree <!-- af5626b4a114abcb82d63db7c8082c3c4756e51b --> af5626b4a114abcb82d63db7c8082c3c4756e51b blob af5626b Hello, world! <!-- ec947e3dd7a7752d078f1ed0cfde7457b21fef58->af5626b4a114abcb82d63db7c8082c3c4756e51b --> ec947e3dd7a7752d078f1ed0cfde7457b21fef58->af5626b4a114abcb82d63db7c8082c3c4756e51b hello.txt <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc --> fa88648dfe0b66e674835fe10bbee36116cfa9bc commit fa88648 tree 130f7a9      parent ccfdafa    author [...]                       updated README.md <!-- 130f7a90b97d88faa2f1ddf794ef7dace1c0c450 --> 130f7a90b97d88faa2f1ddf794ef7dace1c0c450 tree 130f7a9 blob README.md (1bce388) <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc->130f7a90b97d88faa2f1ddf794ef7dace1c0c450 --> fa88648dfe0b66e674835fe10bbee36116cfa9bc->130f7a90b97d88faa2f1ddf794ef7dace1c0c450 tree <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c --> ccfdafa28c04051e0b95f45afec59e259d27d05c commit ccfdafa tree ebfd749                  parent bb2150a                author [...]                                               rename hello.txt to README.md <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc->ccfdafa28c04051e0b95f45afec59e259d27d05c --> fa88648dfe0b66e674835fe10bbee36116cfa9bc->ccfdafa28c04051e0b95f45afec59e259d27d05c parent <!-- 1bce3880336548b24d6aadcc51e3d41359c5908d --> 1bce3880336548b24d6aadcc51e3d41359c5908d blob 1bce388 Hello, planet!                Git is awesome! <!-- 130f7a90b97d88faa2f1ddf794ef7dace1c0c450->1bce3880336548b24d6aadcc51e3d41359c5908d --> 130f7a90b97d88faa2f1ddf794ef7dace1c0c450->1bce3880336548b24d6aadcc51e3d41359c5908d README.md <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c->bb2150aa043e460938c2cd2a21201ea906a689c1 --> ccfdafa28c04051e0b95f45afec59e259d27d05c->bb2150aa043e460938c2cd2a21201ea906a689c1 parent <!-- ebfd7499dae526dcbaf30e1250b1f875946729f8 --> ebfd7499dae526dcbaf30e1250b1f875946729f8 tree ebfd749 blob README.md (af5626b) <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c->ebfd7499dae526dcbaf30e1250b1f875946729f8 --> ccfdafa28c04051e0b95f45afec59e259d27d05c->ebfd7499dae526dcbaf30e1250b1f875946729f8 tree <!-- ebfd7499dae526dcbaf30e1250b1f875946729f8->af5626b4a114abcb82d63db7c8082c3c4756e51b --> ebfd7499dae526dcbaf30e1250b1f875946729f8->af5626b4a114abcb82d63db7c8082c3c4756e51b README.md <!-- ref:main --> ref:main main <!-- ref:main->fa88648dfe0b66e674835fe10bbee36116cfa9bc --> ref:main->fa88648dfe0b66e674835fe10bbee36116cfa9bc <!-- ref:HEAD --> ref:HEAD HEAD <!-- ref:HEAD->bb2150aa043e460938c2cd2a21201ea906a689c1 --> ref:HEAD->bb2150aa043e460938c2cd2a21201ea906a689c1

Du kannst sehen, dass der main-Branch unverändert bleibt und weiterhin auf den neuesten Commit zeigt. Allerdings zeigt HEAD jetzt auf den älteren Commit, den wir ausgecheckt haben (weshalb wir jetzt wieder den ursprünglichen Inhalt des Repositories sehen).

Um wieder zum main-Branch zurückzukehren, kannst du den Befehl git checkout main verwenden:

git checkout main meldet Previous HEAD position was … initial commit und Switched to branch »main«.

Damit sind wir wieder im neuesten Zustand des Repositories. Der Git-Objektgraph sieht jetzt wieder folgendermaßen aus:

git_objects <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc --> fa88648dfe0b66e674835fe10bbee36116cfa9bc commit fa88648 tree 130f7a9      parent ccfdafa    author [...]                       updated README.md <!-- 130f7a90b97d88faa2f1ddf794ef7dace1c0c450 --> 130f7a90b97d88faa2f1ddf794ef7dace1c0c450 tree 130f7a9 blob README.md (1bce388) <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc->130f7a90b97d88faa2f1ddf794ef7dace1c0c450 --> fa88648dfe0b66e674835fe10bbee36116cfa9bc->130f7a90b97d88faa2f1ddf794ef7dace1c0c450 tree <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c --> ccfdafa28c04051e0b95f45afec59e259d27d05c commit ccfdafa tree ebfd749                  parent bb2150a                author [...]                                               rename hello.txt to README.md <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc->ccfdafa28c04051e0b95f45afec59e259d27d05c --> fa88648dfe0b66e674835fe10bbee36116cfa9bc->ccfdafa28c04051e0b95f45afec59e259d27d05c parent <!-- 1bce3880336548b24d6aadcc51e3d41359c5908d --> 1bce3880336548b24d6aadcc51e3d41359c5908d blob 1bce388 Hello, planet!                Git is awesome! <!-- 130f7a90b97d88faa2f1ddf794ef7dace1c0c450->1bce3880336548b24d6aadcc51e3d41359c5908d --> 130f7a90b97d88faa2f1ddf794ef7dace1c0c450->1bce3880336548b24d6aadcc51e3d41359c5908d README.md <!-- ebfd7499dae526dcbaf30e1250b1f875946729f8 --> ebfd7499dae526dcbaf30e1250b1f875946729f8 tree ebfd749 blob README.md (af5626b) <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c->ebfd7499dae526dcbaf30e1250b1f875946729f8 --> ccfdafa28c04051e0b95f45afec59e259d27d05c->ebfd7499dae526dcbaf30e1250b1f875946729f8 tree <!-- bb2150aa043e460938c2cd2a21201ea906a689c1 --> bb2150aa043e460938c2cd2a21201ea906a689c1 commit bb2150a tree ec947e3   author [...]                 initial commit <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c->bb2150aa043e460938c2cd2a21201ea906a689c1 --> ccfdafa28c04051e0b95f45afec59e259d27d05c->bb2150aa043e460938c2cd2a21201ea906a689c1 parent <!-- af5626b4a114abcb82d63db7c8082c3c4756e51b --> af5626b4a114abcb82d63db7c8082c3c4756e51b blob af5626b Hello, world! <!-- ebfd7499dae526dcbaf30e1250b1f875946729f8->af5626b4a114abcb82d63db7c8082c3c4756e51b --> ebfd7499dae526dcbaf30e1250b1f875946729f8->af5626b4a114abcb82d63db7c8082c3c4756e51b README.md <!-- ec947e3dd7a7752d078f1ed0cfde7457b21fef58 --> ec947e3dd7a7752d078f1ed0cfde7457b21fef58 tree ec947e3 blob hello.txt (af5626b) <!-- bb2150aa043e460938c2cd2a21201ea906a689c1->ec947e3dd7a7752d078f1ed0cfde7457b21fef58 --> bb2150aa043e460938c2cd2a21201ea906a689c1->ec947e3dd7a7752d078f1ed0cfde7457b21fef58 tree <!-- ec947e3dd7a7752d078f1ed0cfde7457b21fef58->af5626b4a114abcb82d63db7c8082c3c4756e51b --> ec947e3dd7a7752d078f1ed0cfde7457b21fef58->af5626b4a114abcb82d63db7c8082c3c4756e51b hello.txt <!-- ref:main --> ref:main main <!-- ref:main->fa88648dfe0b66e674835fe10bbee36116cfa9bc --> ref:main->fa88648dfe0b66e674835fe10bbee36116cfa9bc <!-- ref:HEAD --> ref:HEAD HEAD <!-- ref:HEAD->ref:main --> ref:HEAD->ref:main

3. Branches

Kennst du die Situation, in der du eine neue Funktion zu deinem Projekt hinzufügen möchtest, aber nicht sicher bist, ob sie funktioniert? Mit Git kannst du einen neuen Branch erstellen, um an dieser Funktion zu arbeiten, ohne den Haupt-Branch zu beeinflussen. Wenn die Funktion fertig ist und gut funktioniert, kannst du sie wieder in den Haupt-Branch zusammenführen (mergen). Anderenfalls kannst du den Branch einfach löschen, ohne dass dein Haupt-Branch davon betroffen ist.

Einen Branch anlegen

Wir erstellen jetzt einen neuen Branch namens feature und wechseln zu diesem Branch:

git branch feature
git checkout feature

git branch feature und git checkout feature, das »Switched to branch »feature«« meldet.

Der Git-Objektgraph sieht jetzt folgendermaßen aus:

git_objects <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc --> fa88648dfe0b66e674835fe10bbee36116cfa9bc commit fa88648 tree 130f7a9      parent ccfdafa    author [...]                       updated README.md <!-- 130f7a90b97d88faa2f1ddf794ef7dace1c0c450 --> 130f7a90b97d88faa2f1ddf794ef7dace1c0c450 tree 130f7a9 blob README.md (1bce388) <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc->130f7a90b97d88faa2f1ddf794ef7dace1c0c450 --> fa88648dfe0b66e674835fe10bbee36116cfa9bc->130f7a90b97d88faa2f1ddf794ef7dace1c0c450 tree <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c --> ccfdafa28c04051e0b95f45afec59e259d27d05c commit ccfdafa tree ebfd749                  parent bb2150a                author [...]                                               rename hello.txt to README.md <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc->ccfdafa28c04051e0b95f45afec59e259d27d05c --> fa88648dfe0b66e674835fe10bbee36116cfa9bc->ccfdafa28c04051e0b95f45afec59e259d27d05c parent <!-- 1bce3880336548b24d6aadcc51e3d41359c5908d --> 1bce3880336548b24d6aadcc51e3d41359c5908d blob 1bce388 Hello, planet!                Git is awesome! <!-- 130f7a90b97d88faa2f1ddf794ef7dace1c0c450->1bce3880336548b24d6aadcc51e3d41359c5908d --> 130f7a90b97d88faa2f1ddf794ef7dace1c0c450->1bce3880336548b24d6aadcc51e3d41359c5908d README.md <!-- ebfd7499dae526dcbaf30e1250b1f875946729f8 --> ebfd7499dae526dcbaf30e1250b1f875946729f8 tree ebfd749 blob README.md (af5626b) <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c->ebfd7499dae526dcbaf30e1250b1f875946729f8 --> ccfdafa28c04051e0b95f45afec59e259d27d05c->ebfd7499dae526dcbaf30e1250b1f875946729f8 tree <!-- bb2150aa043e460938c2cd2a21201ea906a689c1 --> bb2150aa043e460938c2cd2a21201ea906a689c1 commit bb2150a tree ec947e3   author [...]                 initial commit <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c->bb2150aa043e460938c2cd2a21201ea906a689c1 --> ccfdafa28c04051e0b95f45afec59e259d27d05c->bb2150aa043e460938c2cd2a21201ea906a689c1 parent <!-- af5626b4a114abcb82d63db7c8082c3c4756e51b --> af5626b4a114abcb82d63db7c8082c3c4756e51b blob af5626b Hello, world! <!-- ebfd7499dae526dcbaf30e1250b1f875946729f8->af5626b4a114abcb82d63db7c8082c3c4756e51b --> ebfd7499dae526dcbaf30e1250b1f875946729f8->af5626b4a114abcb82d63db7c8082c3c4756e51b README.md <!-- ec947e3dd7a7752d078f1ed0cfde7457b21fef58 --> ec947e3dd7a7752d078f1ed0cfde7457b21fef58 tree ec947e3 blob hello.txt (af5626b) <!-- bb2150aa043e460938c2cd2a21201ea906a689c1->ec947e3dd7a7752d078f1ed0cfde7457b21fef58 --> bb2150aa043e460938c2cd2a21201ea906a689c1->ec947e3dd7a7752d078f1ed0cfde7457b21fef58 tree <!-- ec947e3dd7a7752d078f1ed0cfde7457b21fef58->af5626b4a114abcb82d63db7c8082c3c4756e51b --> ec947e3dd7a7752d078f1ed0cfde7457b21fef58->af5626b4a114abcb82d63db7c8082c3c4756e51b hello.txt <!-- ref:feature --> ref:feature feature <!-- ref:feature->fa88648dfe0b66e674835fe10bbee36116cfa9bc --> ref:feature->fa88648dfe0b66e674835fe10bbee36116cfa9bc <!-- ref:main --> ref:main main <!-- ref:main->fa88648dfe0b66e674835fe10bbee36116cfa9bc --> ref:main->fa88648dfe0b66e674835fe10bbee36116cfa9bc <!-- ref:HEAD --> ref:HEAD HEAD <!-- ref:HEAD->ref:feature --> ref:HEAD->ref:feature

Es ist also alles wie zuvor, nur dass HEAD jetzt auf den neuen Branch feature zeigt. Erstelle eine neue Datei namens hello.c mit folgendem Inhalt:

#include <stdio.h>

int main() {
    printf("Hello, World!\n");
    return 0;
}

Compiliere die Datei, um ein ausführbares Programm zu erhalten:

gcc -o hello hello.c

…und führe das Programm anschließend aus:

./hello

Du solltest die Meldung Hello, World! im Terminal sehen:

gcc -o hello hello.c übersetzt das Programm, ./hello gibt Hello, World! aus.

Überprüfe den Status mit git status:

git status auf dem Branch feature zeigt hello und hello.c in Rot als Untracked files.

Es sind zwei neue Dateien vorhanden: hello.c und hello (die ausführbare Datei). Wir möchten nur die Quellcodedatei hello.c zur Versionskontrolle hinzufügen, nicht jedoch die ausführbare Datei hello.

Aufgabe 5Warum .gitignore?

Warum soll die ausführbare Datei hello nicht ins Repository?

Warum .gitignore?

Dateien ignorieren

Es gibt bestimmte Arten von Dateien, die nicht in ein Git-Repository aufgenommen werden sollten, wie z.B. ausführbare Dateien, temporäre Dateien oder sensible Informationen wie Zugangsdaten oder API-Schlüssel. Um zu verhindern, dass solche Dateien versehentlich hinzugefügt werden, kannst du eine Datei namens .gitignore erstellen. In dieser Datei kannst du Muster definieren, die Git anweisen, bestimmte Dateien oder Verzeichnisse zu ignorieren. Und genau das machen wir jetzt.

Erstelle eine Datei namens .gitignore mit folgendem Inhalt:

hello
Du kannst auch Platzhalter verwenden, um mehrere Dateien oder Verzeichnisse zu ignorieren. Zum Beispiel ignoriert *.log alle Dateien mit der Endung .log, und temp/ ignoriert das gesamte Verzeichnis temp.

Speichere die Datei und überprüfe den Status erneut mit git status:

Mit der .gitignore zeigt git status nur noch .gitignore und hello.c als Untracked files, hello fehlt.

Jetzt wird nur noch die Datei hello.c als untracked angezeigt, während die ausführbare Datei hello ignoriert wird. Aber auch die Datei .gitignore selbst ist noch nicht zur Versionskontrolle hinzugefügt worden, was wir unbedingt nachholen sollten. Füge beide Dateien zur Staging Area hinzu:

git add .gitignore hello.c

Erstelle anschließend einen neuen Commit:

git commit -m "added hello.c"

git add .gitignore hello.c und git commit -m "added hello.c" auf feature, 2 files changed, 7 insertions.

Der Git-Objektgraph sieht jetzt folgendermaßen aus:

git_objects <!-- e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f --> e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f commit e4eef8f tree 88edcc2   parent fa88648 author [...]                 added hello.c <!-- 88edcc251361e1101bb396061eb6b2d840b37b3e --> 88edcc251361e1101bb396061eb6b2d840b37b3e tree 88edcc2 blob .gitignore (ce01362) blob README.md (1bce388) blob hello.c (8321e25)    <!-- e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f->88edcc251361e1101bb396061eb6b2d840b37b3e --> e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f->88edcc251361e1101bb396061eb6b2d840b37b3e tree <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc --> fa88648dfe0b66e674835fe10bbee36116cfa9bc commit fa88648 tree 130f7a9      parent ccfdafa    author [...]                       updated README.md <!-- e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f->fa88648dfe0b66e674835fe10bbee36116cfa9bc --> e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f->fa88648dfe0b66e674835fe10bbee36116cfa9bc parent <!-- ce013625030ba8dba906f756967f9e9ca394464a --> ce013625030ba8dba906f756967f9e9ca394464a blob ce01362 hello <!-- 88edcc251361e1101bb396061eb6b2d840b37b3e->ce013625030ba8dba906f756967f9e9ca394464a --> 88edcc251361e1101bb396061eb6b2d840b37b3e->ce013625030ba8dba906f756967f9e9ca394464a .gitignore <!-- 1bce3880336548b24d6aadcc51e3d41359c5908d --> 1bce3880336548b24d6aadcc51e3d41359c5908d blob 1bce388 Hello, planet!                Git is awesome! <!-- 88edcc251361e1101bb396061eb6b2d840b37b3e->1bce3880336548b24d6aadcc51e3d41359c5908d --> 88edcc251361e1101bb396061eb6b2d840b37b3e->1bce3880336548b24d6aadcc51e3d41359c5908d README.md <!-- 8321e25d3174e06bfbcc553517e13c5250e25772 --> 8321e25d3174e06bfbcc553517e13c5250e25772 blob 8321e25 #include <stdio.h>                                           int main() {                      printf("Hello, World!\n");    return 0;                  }                              <!-- 88edcc251361e1101bb396061eb6b2d840b37b3e->8321e25d3174e06bfbcc553517e13c5250e25772 --> 88edcc251361e1101bb396061eb6b2d840b37b3e->8321e25d3174e06bfbcc553517e13c5250e25772 hello.c <!-- 130f7a90b97d88faa2f1ddf794ef7dace1c0c450 --> 130f7a90b97d88faa2f1ddf794ef7dace1c0c450 tree 130f7a9 blob README.md (1bce388) <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc->130f7a90b97d88faa2f1ddf794ef7dace1c0c450 --> fa88648dfe0b66e674835fe10bbee36116cfa9bc->130f7a90b97d88faa2f1ddf794ef7dace1c0c450 tree <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c --> ccfdafa28c04051e0b95f45afec59e259d27d05c commit ccfdafa tree ebfd749                  parent bb2150a                author [...]                                               rename hello.txt to README.md <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc->ccfdafa28c04051e0b95f45afec59e259d27d05c --> fa88648dfe0b66e674835fe10bbee36116cfa9bc->ccfdafa28c04051e0b95f45afec59e259d27d05c parent <!-- 130f7a90b97d88faa2f1ddf794ef7dace1c0c450->1bce3880336548b24d6aadcc51e3d41359c5908d --> 130f7a90b97d88faa2f1ddf794ef7dace1c0c450->1bce3880336548b24d6aadcc51e3d41359c5908d README.md <!-- ebfd7499dae526dcbaf30e1250b1f875946729f8 --> ebfd7499dae526dcbaf30e1250b1f875946729f8 tree ebfd749 blob README.md (af5626b) <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c->ebfd7499dae526dcbaf30e1250b1f875946729f8 --> ccfdafa28c04051e0b95f45afec59e259d27d05c->ebfd7499dae526dcbaf30e1250b1f875946729f8 tree <!-- bb2150aa043e460938c2cd2a21201ea906a689c1 --> bb2150aa043e460938c2cd2a21201ea906a689c1 commit bb2150a tree ec947e3   author [...]                 initial commit <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c->bb2150aa043e460938c2cd2a21201ea906a689c1 --> ccfdafa28c04051e0b95f45afec59e259d27d05c->bb2150aa043e460938c2cd2a21201ea906a689c1 parent <!-- af5626b4a114abcb82d63db7c8082c3c4756e51b --> af5626b4a114abcb82d63db7c8082c3c4756e51b blob af5626b Hello, world! <!-- ebfd7499dae526dcbaf30e1250b1f875946729f8->af5626b4a114abcb82d63db7c8082c3c4756e51b --> ebfd7499dae526dcbaf30e1250b1f875946729f8->af5626b4a114abcb82d63db7c8082c3c4756e51b README.md <!-- ec947e3dd7a7752d078f1ed0cfde7457b21fef58 --> ec947e3dd7a7752d078f1ed0cfde7457b21fef58 tree ec947e3 blob hello.txt (af5626b) <!-- bb2150aa043e460938c2cd2a21201ea906a689c1->ec947e3dd7a7752d078f1ed0cfde7457b21fef58 --> bb2150aa043e460938c2cd2a21201ea906a689c1->ec947e3dd7a7752d078f1ed0cfde7457b21fef58 tree <!-- ec947e3dd7a7752d078f1ed0cfde7457b21fef58->af5626b4a114abcb82d63db7c8082c3c4756e51b --> ec947e3dd7a7752d078f1ed0cfde7457b21fef58->af5626b4a114abcb82d63db7c8082c3c4756e51b hello.txt <!-- ref:feature --> ref:feature feature <!-- ref:feature->e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f --> ref:feature->e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f <!-- ref:main --> ref:main main <!-- ref:main->fa88648dfe0b66e674835fe10bbee36116cfa9bc --> ref:main->fa88648dfe0b66e674835fe10bbee36116cfa9bc <!-- ref:HEAD --> ref:HEAD HEAD <!-- ref:HEAD->ref:feature --> ref:HEAD->ref:feature

Wie du sehen kannst, zeigt der feature-Branch jetzt auf den neuesten Commit, der die Dateien hello.c und .gitignore enthält. Der main-Branch bleibt unverändert und zeigt weiterhin auf den vorherigen Commit.

Wir können uns anzeigen lassen, welche Branches es gibt und auf welchen Commit sie zeigen, indem wir den Befehl git branch ausführen:

git branch listet feature, grün und mit Stern als aktuellen Branch, und main.

Wie du siehst, gibt es momentan zwei Branches: main und feature. Der Stern (*) zeigt an, dass wir uns gerade im feature-Branch befinden.

In beiden Branches arbeiten

Wir wechseln jetzt zurück zum main-Branch:

git checkout main

Dort passen wir – unabhängig vom feature-Branch – die Datei README.md an und fügen am Ende einen Smiley hinzu:

Hello, planet!

Git is awesome!

:)

README.md im Editor, unter »Git is awesome!« steht jetzt eine neue Zeile mit einem Smiley :).

Wie du in der oberen Leiste sehen kannst, sind die Dateien hello.c und .gitignore, die gerade noch geöffnet waren, jetzt nicht mehr im Arbeitsverzeichnis vorhanden. Das liegt daran, dass wir zu einem anderen Branch gewechselt haben, in dem diese Dateien nicht existieren. Git passt das Arbeitsverzeichnis automatisch an den Zustand des ausgecheckten Branches an.

Es ist eine gute Angewohnheit, vor dem Committen die Änderungen mit git diff zu überprüfen:

Nach git checkout main zeigt git diff für README.md eine Leerzeile und :) in Grün hinzugefügt.

Wir erstellen jetzt einen neuen Commit:

git commit -a -m "added smiley"

git commit -a -m "added smiley" auf main, 1 file changed, 2 insertions.

Der Git-Objektgraph sieht jetzt folgendermaßen aus:

git_objects <!-- dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8 --> dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8 commit dcf8f57 tree 0701bc9   parent fa88648 author [...]                 added smiley   <!-- 0701bc912c4c687059107daf3e9a6b078d1cad18 --> 0701bc912c4c687059107daf3e9a6b078d1cad18 tree 0701bc9 blob README.md (bcd9a8a) <!-- dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8->0701bc912c4c687059107daf3e9a6b078d1cad18 --> dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8->0701bc912c4c687059107daf3e9a6b078d1cad18 tree <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc --> fa88648dfe0b66e674835fe10bbee36116cfa9bc commit fa88648 tree 130f7a9      parent ccfdafa    author [...]                       updated README.md <!-- dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8->fa88648dfe0b66e674835fe10bbee36116cfa9bc --> dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8->fa88648dfe0b66e674835fe10bbee36116cfa9bc parent <!-- bcd9a8a4d1fc1d4deb7ab91877a0874fe869acf3 --> bcd9a8a4d1fc1d4deb7ab91877a0874fe869acf3 blob bcd9a8a Hello, planet!                Git is awesome!                :)              <!-- 0701bc912c4c687059107daf3e9a6b078d1cad18->bcd9a8a4d1fc1d4deb7ab91877a0874fe869acf3 --> 0701bc912c4c687059107daf3e9a6b078d1cad18->bcd9a8a4d1fc1d4deb7ab91877a0874fe869acf3 README.md <!-- 130f7a90b97d88faa2f1ddf794ef7dace1c0c450 --> 130f7a90b97d88faa2f1ddf794ef7dace1c0c450 tree 130f7a9 blob README.md (1bce388) <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc->130f7a90b97d88faa2f1ddf794ef7dace1c0c450 --> fa88648dfe0b66e674835fe10bbee36116cfa9bc->130f7a90b97d88faa2f1ddf794ef7dace1c0c450 tree <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c --> ccfdafa28c04051e0b95f45afec59e259d27d05c commit ccfdafa tree ebfd749                  parent bb2150a                author [...]                                               rename hello.txt to README.md <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc->ccfdafa28c04051e0b95f45afec59e259d27d05c --> fa88648dfe0b66e674835fe10bbee36116cfa9bc->ccfdafa28c04051e0b95f45afec59e259d27d05c parent <!-- 1bce3880336548b24d6aadcc51e3d41359c5908d --> 1bce3880336548b24d6aadcc51e3d41359c5908d blob 1bce388 Hello, planet!                Git is awesome! <!-- 130f7a90b97d88faa2f1ddf794ef7dace1c0c450->1bce3880336548b24d6aadcc51e3d41359c5908d --> 130f7a90b97d88faa2f1ddf794ef7dace1c0c450->1bce3880336548b24d6aadcc51e3d41359c5908d README.md <!-- ebfd7499dae526dcbaf30e1250b1f875946729f8 --> ebfd7499dae526dcbaf30e1250b1f875946729f8 tree ebfd749 blob README.md (af5626b) <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c->ebfd7499dae526dcbaf30e1250b1f875946729f8 --> ccfdafa28c04051e0b95f45afec59e259d27d05c->ebfd7499dae526dcbaf30e1250b1f875946729f8 tree <!-- bb2150aa043e460938c2cd2a21201ea906a689c1 --> bb2150aa043e460938c2cd2a21201ea906a689c1 commit bb2150a tree ec947e3   author [...]                 initial commit <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c->bb2150aa043e460938c2cd2a21201ea906a689c1 --> ccfdafa28c04051e0b95f45afec59e259d27d05c->bb2150aa043e460938c2cd2a21201ea906a689c1 parent <!-- af5626b4a114abcb82d63db7c8082c3c4756e51b --> af5626b4a114abcb82d63db7c8082c3c4756e51b blob af5626b Hello, world! <!-- ebfd7499dae526dcbaf30e1250b1f875946729f8->af5626b4a114abcb82d63db7c8082c3c4756e51b --> ebfd7499dae526dcbaf30e1250b1f875946729f8->af5626b4a114abcb82d63db7c8082c3c4756e51b README.md <!-- ec947e3dd7a7752d078f1ed0cfde7457b21fef58 --> ec947e3dd7a7752d078f1ed0cfde7457b21fef58 tree ec947e3 blob hello.txt (af5626b) <!-- bb2150aa043e460938c2cd2a21201ea906a689c1->ec947e3dd7a7752d078f1ed0cfde7457b21fef58 --> bb2150aa043e460938c2cd2a21201ea906a689c1->ec947e3dd7a7752d078f1ed0cfde7457b21fef58 tree <!-- ec947e3dd7a7752d078f1ed0cfde7457b21fef58->af5626b4a114abcb82d63db7c8082c3c4756e51b --> ec947e3dd7a7752d078f1ed0cfde7457b21fef58->af5626b4a114abcb82d63db7c8082c3c4756e51b hello.txt <!-- e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f --> e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f commit e4eef8f tree 88edcc2   parent fa88648 author [...]                 added hello.c <!-- e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f->fa88648dfe0b66e674835fe10bbee36116cfa9bc --> e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f->fa88648dfe0b66e674835fe10bbee36116cfa9bc parent <!-- 88edcc251361e1101bb396061eb6b2d840b37b3e --> 88edcc251361e1101bb396061eb6b2d840b37b3e tree 88edcc2 blob .gitignore (ce01362) blob README.md (1bce388) blob hello.c (8321e25)    <!-- e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f->88edcc251361e1101bb396061eb6b2d840b37b3e --> e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f->88edcc251361e1101bb396061eb6b2d840b37b3e tree <!-- 88edcc251361e1101bb396061eb6b2d840b37b3e->1bce3880336548b24d6aadcc51e3d41359c5908d --> 88edcc251361e1101bb396061eb6b2d840b37b3e->1bce3880336548b24d6aadcc51e3d41359c5908d README.md <!-- ce013625030ba8dba906f756967f9e9ca394464a --> ce013625030ba8dba906f756967f9e9ca394464a blob ce01362 hello <!-- 88edcc251361e1101bb396061eb6b2d840b37b3e->ce013625030ba8dba906f756967f9e9ca394464a --> 88edcc251361e1101bb396061eb6b2d840b37b3e->ce013625030ba8dba906f756967f9e9ca394464a .gitignore <!-- 8321e25d3174e06bfbcc553517e13c5250e25772 --> 8321e25d3174e06bfbcc553517e13c5250e25772 blob 8321e25 #include <stdio.h>                                           int main() {                      printf("Hello, World!\n");    return 0;                  }                              <!-- 88edcc251361e1101bb396061eb6b2d840b37b3e->8321e25d3174e06bfbcc553517e13c5250e25772 --> 88edcc251361e1101bb396061eb6b2d840b37b3e->8321e25d3174e06bfbcc553517e13c5250e25772 hello.c <!-- ref:feature --> ref:feature feature <!-- ref:feature->e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f --> ref:feature->e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f <!-- ref:main --> ref:main main <!-- ref:main->dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8 --> ref:main->dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8 <!-- ref:HEAD --> ref:HEAD HEAD <!-- ref:HEAD->ref:main --> ref:HEAD->ref:main

Wir haben jetzt an beiden Branches unabhängig voneinander Änderungen vorgenommen. Der main-Branch enthält den neuesten Commit mit der aktualisierten README.md-Datei, während der feature-Branch den neuesten Commit mit der hello.c-Datei enthält.

Branches zusammenführen

Wir könnten uns jetzt dazu entscheiden, die Änderungen aus dem feature-Branch zu verwerfen, indem wir den Branch einfach löschen. In diesem Fall würden die Änderungen an der Datei hello.c verloren gehen, da sie nur im feature-Branch existieren.

Wir möchten die Änderungen aus dem feature-Branch jedoch in den main-Branch übernehmen. Dazu verwenden wir den Befehl git merge, während wir uns im main-Branch befinden. Wie bei git commit gibst du mit -m die Nachricht für den Merge-Commit an; ohne -m öffnet Git dafür einen Editor:

git merge feature -m "merged feature into main"

git merge feature -m "merged feature into main" meldet Merge made by the »ort« strategy, mit .gitignore und hello.c, 2 files changed, 7 insertions.

Beim Mergen ist es wichtig, dass du dich im Ziel-Branch befindest, also in dem Branch, in den du die Änderungen übernehmen möchtest. In diesem Fall möchten wir die Änderungen aus dem feature-Branch in den main-Branch übernehmen, also müssen wir uns zuerst im main-Branch befinden, bevor wir den Merge-Befehl ausführen.

Der Git-Objektgraph sieht jetzt folgendermaßen aus:

git_objects <!-- f1b6d1808c7dd0302968ce90db2a3c43ae17e85f --> f1b6d1808c7dd0302968ce90db2a3c43ae17e85f commit f1b6d18 tree 7039bfe             parent dcf8f57           parent e4eef8f           author [...]                                     merged feature into main <!-- 7039bfe2009981e25d3dba7c4f5dfbd933213dfd --> 7039bfe2009981e25d3dba7c4f5dfbd933213dfd tree 7039bfe blob .gitignore (ce01362) blob README.md (bcd9a8a) blob hello.c (8321e25)    <!-- f1b6d1808c7dd0302968ce90db2a3c43ae17e85f->7039bfe2009981e25d3dba7c4f5dfbd933213dfd --> f1b6d1808c7dd0302968ce90db2a3c43ae17e85f->7039bfe2009981e25d3dba7c4f5dfbd933213dfd tree <!-- dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8 --> dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8 commit dcf8f57 tree 0701bc9   parent fa88648 author [...]                 added smiley   <!-- f1b6d1808c7dd0302968ce90db2a3c43ae17e85f->dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8 --> f1b6d1808c7dd0302968ce90db2a3c43ae17e85f->dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8 parent <!-- e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f --> e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f commit e4eef8f tree 88edcc2   parent fa88648 author [...]                 added hello.c <!-- f1b6d1808c7dd0302968ce90db2a3c43ae17e85f->e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f --> f1b6d1808c7dd0302968ce90db2a3c43ae17e85f->e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f parent <!-- ce013625030ba8dba906f756967f9e9ca394464a --> ce013625030ba8dba906f756967f9e9ca394464a blob ce01362 hello <!-- 7039bfe2009981e25d3dba7c4f5dfbd933213dfd->ce013625030ba8dba906f756967f9e9ca394464a --> 7039bfe2009981e25d3dba7c4f5dfbd933213dfd->ce013625030ba8dba906f756967f9e9ca394464a .gitignore <!-- bcd9a8a4d1fc1d4deb7ab91877a0874fe869acf3 --> bcd9a8a4d1fc1d4deb7ab91877a0874fe869acf3 blob bcd9a8a Hello, planet!                Git is awesome!                :)              <!-- 7039bfe2009981e25d3dba7c4f5dfbd933213dfd->bcd9a8a4d1fc1d4deb7ab91877a0874fe869acf3 --> 7039bfe2009981e25d3dba7c4f5dfbd933213dfd->bcd9a8a4d1fc1d4deb7ab91877a0874fe869acf3 README.md <!-- 8321e25d3174e06bfbcc553517e13c5250e25772 --> 8321e25d3174e06bfbcc553517e13c5250e25772 blob 8321e25 #include <stdio.h>                                           int main() {                      printf("Hello, World!\n");    return 0;                  }                              <!-- 7039bfe2009981e25d3dba7c4f5dfbd933213dfd->8321e25d3174e06bfbcc553517e13c5250e25772 --> 7039bfe2009981e25d3dba7c4f5dfbd933213dfd->8321e25d3174e06bfbcc553517e13c5250e25772 hello.c <!-- 0701bc912c4c687059107daf3e9a6b078d1cad18 --> 0701bc912c4c687059107daf3e9a6b078d1cad18 tree 0701bc9 blob README.md (bcd9a8a) <!-- dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8->0701bc912c4c687059107daf3e9a6b078d1cad18 --> dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8->0701bc912c4c687059107daf3e9a6b078d1cad18 tree <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc --> fa88648dfe0b66e674835fe10bbee36116cfa9bc commit fa88648 tree 130f7a9      parent ccfdafa    author [...]                       updated README.md <!-- dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8->fa88648dfe0b66e674835fe10bbee36116cfa9bc --> dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8->fa88648dfe0b66e674835fe10bbee36116cfa9bc parent <!-- e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f->fa88648dfe0b66e674835fe10bbee36116cfa9bc --> e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f->fa88648dfe0b66e674835fe10bbee36116cfa9bc parent <!-- 88edcc251361e1101bb396061eb6b2d840b37b3e --> 88edcc251361e1101bb396061eb6b2d840b37b3e tree 88edcc2 blob .gitignore (ce01362) blob README.md (1bce388) blob hello.c (8321e25)    <!-- e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f->88edcc251361e1101bb396061eb6b2d840b37b3e --> e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f->88edcc251361e1101bb396061eb6b2d840b37b3e tree <!-- 0701bc912c4c687059107daf3e9a6b078d1cad18->bcd9a8a4d1fc1d4deb7ab91877a0874fe869acf3 --> 0701bc912c4c687059107daf3e9a6b078d1cad18->bcd9a8a4d1fc1d4deb7ab91877a0874fe869acf3 README.md <!-- 130f7a90b97d88faa2f1ddf794ef7dace1c0c450 --> 130f7a90b97d88faa2f1ddf794ef7dace1c0c450 tree 130f7a9 blob README.md (1bce388) <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc->130f7a90b97d88faa2f1ddf794ef7dace1c0c450 --> fa88648dfe0b66e674835fe10bbee36116cfa9bc->130f7a90b97d88faa2f1ddf794ef7dace1c0c450 tree <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c --> ccfdafa28c04051e0b95f45afec59e259d27d05c commit ccfdafa tree ebfd749                  parent bb2150a                author [...]                                               rename hello.txt to README.md <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc->ccfdafa28c04051e0b95f45afec59e259d27d05c --> fa88648dfe0b66e674835fe10bbee36116cfa9bc->ccfdafa28c04051e0b95f45afec59e259d27d05c parent <!-- 88edcc251361e1101bb396061eb6b2d840b37b3e->ce013625030ba8dba906f756967f9e9ca394464a --> 88edcc251361e1101bb396061eb6b2d840b37b3e->ce013625030ba8dba906f756967f9e9ca394464a .gitignore <!-- 88edcc251361e1101bb396061eb6b2d840b37b3e->8321e25d3174e06bfbcc553517e13c5250e25772 --> 88edcc251361e1101bb396061eb6b2d840b37b3e->8321e25d3174e06bfbcc553517e13c5250e25772 hello.c <!-- 1bce3880336548b24d6aadcc51e3d41359c5908d --> 1bce3880336548b24d6aadcc51e3d41359c5908d blob 1bce388 Hello, planet!                Git is awesome! <!-- 88edcc251361e1101bb396061eb6b2d840b37b3e->1bce3880336548b24d6aadcc51e3d41359c5908d --> 88edcc251361e1101bb396061eb6b2d840b37b3e->1bce3880336548b24d6aadcc51e3d41359c5908d README.md <!-- 130f7a90b97d88faa2f1ddf794ef7dace1c0c450->1bce3880336548b24d6aadcc51e3d41359c5908d --> 130f7a90b97d88faa2f1ddf794ef7dace1c0c450->1bce3880336548b24d6aadcc51e3d41359c5908d README.md <!-- ebfd7499dae526dcbaf30e1250b1f875946729f8 --> ebfd7499dae526dcbaf30e1250b1f875946729f8 tree ebfd749 blob README.md (af5626b) <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c->ebfd7499dae526dcbaf30e1250b1f875946729f8 --> ccfdafa28c04051e0b95f45afec59e259d27d05c->ebfd7499dae526dcbaf30e1250b1f875946729f8 tree <!-- bb2150aa043e460938c2cd2a21201ea906a689c1 --> bb2150aa043e460938c2cd2a21201ea906a689c1 commit bb2150a tree ec947e3   author [...]                 initial commit <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c->bb2150aa043e460938c2cd2a21201ea906a689c1 --> ccfdafa28c04051e0b95f45afec59e259d27d05c->bb2150aa043e460938c2cd2a21201ea906a689c1 parent <!-- af5626b4a114abcb82d63db7c8082c3c4756e51b --> af5626b4a114abcb82d63db7c8082c3c4756e51b blob af5626b Hello, world! <!-- ebfd7499dae526dcbaf30e1250b1f875946729f8->af5626b4a114abcb82d63db7c8082c3c4756e51b --> ebfd7499dae526dcbaf30e1250b1f875946729f8->af5626b4a114abcb82d63db7c8082c3c4756e51b README.md <!-- ec947e3dd7a7752d078f1ed0cfde7457b21fef58 --> ec947e3dd7a7752d078f1ed0cfde7457b21fef58 tree ec947e3 blob hello.txt (af5626b) <!-- bb2150aa043e460938c2cd2a21201ea906a689c1->ec947e3dd7a7752d078f1ed0cfde7457b21fef58 --> bb2150aa043e460938c2cd2a21201ea906a689c1->ec947e3dd7a7752d078f1ed0cfde7457b21fef58 tree <!-- ec947e3dd7a7752d078f1ed0cfde7457b21fef58->af5626b4a114abcb82d63db7c8082c3c4756e51b --> ec947e3dd7a7752d078f1ed0cfde7457b21fef58->af5626b4a114abcb82d63db7c8082c3c4756e51b hello.txt <!-- ref:feature --> ref:feature feature <!-- ref:feature->e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f --> ref:feature->e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f <!-- ref:main --> ref:main main <!-- ref:main->f1b6d1808c7dd0302968ce90db2a3c43ae17e85f --> ref:main->f1b6d1808c7dd0302968ce90db2a3c43ae17e85f <!-- ref:HEAD --> ref:HEAD HEAD <!-- ref:HEAD->ref:main --> ref:HEAD->ref:main

Wie du sehen kannst, wurde ein neuer Merge-Commit erstellt, der zwei Vorgänger hat: den vormals neuesten Commit im main-Branch und den neuesten Commit im feature-Branch. Der Merge-Commit kombiniert die Änderungen aus beiden Branches.

Der main-Branch zeigt jetzt auf das aktuelle, vollständige Projekt und wir können den Feature-Branch löschen, da wir ihn nicht mehr benötigen:

git branch -d feature

git branch -d feature meldet Deleted branch feature.

Der Git-Objektgraph sieht jetzt folgendermaßen aus:

git_objects <!-- f1b6d1808c7dd0302968ce90db2a3c43ae17e85f --> f1b6d1808c7dd0302968ce90db2a3c43ae17e85f commit f1b6d18 tree 7039bfe             parent dcf8f57           parent e4eef8f           author [...]                                     merged feature into main <!-- 7039bfe2009981e25d3dba7c4f5dfbd933213dfd --> 7039bfe2009981e25d3dba7c4f5dfbd933213dfd tree 7039bfe blob .gitignore (ce01362) blob README.md (bcd9a8a) blob hello.c (8321e25)    <!-- f1b6d1808c7dd0302968ce90db2a3c43ae17e85f->7039bfe2009981e25d3dba7c4f5dfbd933213dfd --> f1b6d1808c7dd0302968ce90db2a3c43ae17e85f->7039bfe2009981e25d3dba7c4f5dfbd933213dfd tree <!-- dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8 --> dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8 commit dcf8f57 tree 0701bc9   parent fa88648 author [...]                 added smiley   <!-- f1b6d1808c7dd0302968ce90db2a3c43ae17e85f->dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8 --> f1b6d1808c7dd0302968ce90db2a3c43ae17e85f->dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8 parent <!-- e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f --> e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f commit e4eef8f tree 88edcc2   parent fa88648 author [...]                 added hello.c <!-- f1b6d1808c7dd0302968ce90db2a3c43ae17e85f->e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f --> f1b6d1808c7dd0302968ce90db2a3c43ae17e85f->e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f parent <!-- ce013625030ba8dba906f756967f9e9ca394464a --> ce013625030ba8dba906f756967f9e9ca394464a blob ce01362 hello <!-- 7039bfe2009981e25d3dba7c4f5dfbd933213dfd->ce013625030ba8dba906f756967f9e9ca394464a --> 7039bfe2009981e25d3dba7c4f5dfbd933213dfd->ce013625030ba8dba906f756967f9e9ca394464a .gitignore <!-- bcd9a8a4d1fc1d4deb7ab91877a0874fe869acf3 --> bcd9a8a4d1fc1d4deb7ab91877a0874fe869acf3 blob bcd9a8a Hello, planet!                Git is awesome!                :)              <!-- 7039bfe2009981e25d3dba7c4f5dfbd933213dfd->bcd9a8a4d1fc1d4deb7ab91877a0874fe869acf3 --> 7039bfe2009981e25d3dba7c4f5dfbd933213dfd->bcd9a8a4d1fc1d4deb7ab91877a0874fe869acf3 README.md <!-- 8321e25d3174e06bfbcc553517e13c5250e25772 --> 8321e25d3174e06bfbcc553517e13c5250e25772 blob 8321e25 #include <stdio.h>                                           int main() {                      printf("Hello, World!\n");    return 0;                  }                              <!-- 7039bfe2009981e25d3dba7c4f5dfbd933213dfd->8321e25d3174e06bfbcc553517e13c5250e25772 --> 7039bfe2009981e25d3dba7c4f5dfbd933213dfd->8321e25d3174e06bfbcc553517e13c5250e25772 hello.c <!-- 0701bc912c4c687059107daf3e9a6b078d1cad18 --> 0701bc912c4c687059107daf3e9a6b078d1cad18 tree 0701bc9 blob README.md (bcd9a8a) <!-- dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8->0701bc912c4c687059107daf3e9a6b078d1cad18 --> dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8->0701bc912c4c687059107daf3e9a6b078d1cad18 tree <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc --> fa88648dfe0b66e674835fe10bbee36116cfa9bc commit fa88648 tree 130f7a9      parent ccfdafa    author [...]                       updated README.md <!-- dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8->fa88648dfe0b66e674835fe10bbee36116cfa9bc --> dcf8f57ddc8710dbc3f802f05a460eb1ec3e91c8->fa88648dfe0b66e674835fe10bbee36116cfa9bc parent <!-- e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f->fa88648dfe0b66e674835fe10bbee36116cfa9bc --> e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f->fa88648dfe0b66e674835fe10bbee36116cfa9bc parent <!-- 88edcc251361e1101bb396061eb6b2d840b37b3e --> 88edcc251361e1101bb396061eb6b2d840b37b3e tree 88edcc2 blob .gitignore (ce01362) blob README.md (1bce388) blob hello.c (8321e25)    <!-- e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f->88edcc251361e1101bb396061eb6b2d840b37b3e --> e4eef8f1385b47a7efc0df4b682dab0f9cbebe0f->88edcc251361e1101bb396061eb6b2d840b37b3e tree <!-- 0701bc912c4c687059107daf3e9a6b078d1cad18->bcd9a8a4d1fc1d4deb7ab91877a0874fe869acf3 --> 0701bc912c4c687059107daf3e9a6b078d1cad18->bcd9a8a4d1fc1d4deb7ab91877a0874fe869acf3 README.md <!-- 130f7a90b97d88faa2f1ddf794ef7dace1c0c450 --> 130f7a90b97d88faa2f1ddf794ef7dace1c0c450 tree 130f7a9 blob README.md (1bce388) <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc->130f7a90b97d88faa2f1ddf794ef7dace1c0c450 --> fa88648dfe0b66e674835fe10bbee36116cfa9bc->130f7a90b97d88faa2f1ddf794ef7dace1c0c450 tree <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c --> ccfdafa28c04051e0b95f45afec59e259d27d05c commit ccfdafa tree ebfd749                  parent bb2150a                author [...]                                               rename hello.txt to README.md <!-- fa88648dfe0b66e674835fe10bbee36116cfa9bc->ccfdafa28c04051e0b95f45afec59e259d27d05c --> fa88648dfe0b66e674835fe10bbee36116cfa9bc->ccfdafa28c04051e0b95f45afec59e259d27d05c parent <!-- 88edcc251361e1101bb396061eb6b2d840b37b3e->ce013625030ba8dba906f756967f9e9ca394464a --> 88edcc251361e1101bb396061eb6b2d840b37b3e->ce013625030ba8dba906f756967f9e9ca394464a .gitignore <!-- 88edcc251361e1101bb396061eb6b2d840b37b3e->8321e25d3174e06bfbcc553517e13c5250e25772 --> 88edcc251361e1101bb396061eb6b2d840b37b3e->8321e25d3174e06bfbcc553517e13c5250e25772 hello.c <!-- 1bce3880336548b24d6aadcc51e3d41359c5908d --> 1bce3880336548b24d6aadcc51e3d41359c5908d blob 1bce388 Hello, planet!                Git is awesome! <!-- 88edcc251361e1101bb396061eb6b2d840b37b3e->1bce3880336548b24d6aadcc51e3d41359c5908d --> 88edcc251361e1101bb396061eb6b2d840b37b3e->1bce3880336548b24d6aadcc51e3d41359c5908d README.md <!-- 130f7a90b97d88faa2f1ddf794ef7dace1c0c450->1bce3880336548b24d6aadcc51e3d41359c5908d --> 130f7a90b97d88faa2f1ddf794ef7dace1c0c450->1bce3880336548b24d6aadcc51e3d41359c5908d README.md <!-- ebfd7499dae526dcbaf30e1250b1f875946729f8 --> ebfd7499dae526dcbaf30e1250b1f875946729f8 tree ebfd749 blob README.md (af5626b) <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c->ebfd7499dae526dcbaf30e1250b1f875946729f8 --> ccfdafa28c04051e0b95f45afec59e259d27d05c->ebfd7499dae526dcbaf30e1250b1f875946729f8 tree <!-- bb2150aa043e460938c2cd2a21201ea906a689c1 --> bb2150aa043e460938c2cd2a21201ea906a689c1 commit bb2150a tree ec947e3   author [...]                 initial commit <!-- ccfdafa28c04051e0b95f45afec59e259d27d05c->bb2150aa043e460938c2cd2a21201ea906a689c1 --> ccfdafa28c04051e0b95f45afec59e259d27d05c->bb2150aa043e460938c2cd2a21201ea906a689c1 parent <!-- af5626b4a114abcb82d63db7c8082c3c4756e51b --> af5626b4a114abcb82d63db7c8082c3c4756e51b blob af5626b Hello, world! <!-- ebfd7499dae526dcbaf30e1250b1f875946729f8->af5626b4a114abcb82d63db7c8082c3c4756e51b --> ebfd7499dae526dcbaf30e1250b1f875946729f8->af5626b4a114abcb82d63db7c8082c3c4756e51b README.md <!-- ec947e3dd7a7752d078f1ed0cfde7457b21fef58 --> ec947e3dd7a7752d078f1ed0cfde7457b21fef58 tree ec947e3 blob hello.txt (af5626b) <!-- bb2150aa043e460938c2cd2a21201ea906a689c1->ec947e3dd7a7752d078f1ed0cfde7457b21fef58 --> bb2150aa043e460938c2cd2a21201ea906a689c1->ec947e3dd7a7752d078f1ed0cfde7457b21fef58 tree <!-- ec947e3dd7a7752d078f1ed0cfde7457b21fef58->af5626b4a114abcb82d63db7c8082c3c4756e51b --> ec947e3dd7a7752d078f1ed0cfde7457b21fef58->af5626b4a114abcb82d63db7c8082c3c4756e51b hello.txt <!-- ref:main --> ref:main main <!-- ref:main->f1b6d1808c7dd0302968ce90db2a3c43ae17e85f --> ref:main->f1b6d1808c7dd0302968ce90db2a3c43ae17e85f <!-- ref:HEAD --> ref:HEAD HEAD <!-- ref:HEAD->ref:main --> ref:HEAD->ref:main

Obwohl es nun keinen feature-Branch mehr gibt, sieht man immer noch Spuren davon im Graphen, da es Verzweigungen und Zusammenführungen in der Versionsgeschichte gibt. Der Vorteil des Feature-Branches war, dass wir isoliert an einer neuen Funktion arbeiten konnten, ohne den Haupt-Branch zu beeinflussen. Nachdem die Funktion fertig war, konnten wir sie problemlos in den Haupt-Branch integrieren.

Aufgabe 6Feature gemergt

Merge den Branch feature in main, sodass git status nichts mehr meldet.

Wenn es dir wichtig ist, dass dein main-Branch stabil bleibt, solltest du den feature-Branch nicht ungeprüft in den main-Branch mergen. Hole vorher alle Änderungen aus dem main-Branch in deinen feature-Branch, teste alles gründlich und führe erst dann den Merge durch.

4. Etwas Luxus im Terminal

Um die Arbeit im Terminal etwas zu vereinfachen, können wir zweierlei tun:

  1. Bash-Completion für Git aktivieren
  2. Einen Git-Prompt einrichten

Um den Git-Prompt zu installieren, klone das Repository bash-git-prompt in dein Home-Verzeichnis:

git clone https://github.com/magicmonty/bash-git-prompt.git ~/.bash-git-prompt --depth=1

Öffne anschließend die Datei /workspace/.bashrc in VS Code und füge am Ende der Datei folgende Zeilen hinzu:

# enable Bash completion for Git
source /usr/share/bash-completion/completions/git

# enable Git prompt
if [ -f "$HOME/.bash-git-prompt/gitprompt.sh" ]; then
    GIT_PROMPT_ONLY_IN_REPO=1
    GIT_PROMPT_VIRTUALENV=""
    source "$HOME/.bash-git-prompt/gitprompt.sh"
fi

Speichere die Datei und lade die Änderungen mit folgendem Befehl neu:

source ~/.bashrc

Jetzt hast du einen speziellen Git-Prompt, wenn du in einem Git-Repository arbeitest. Dieser Prompt zeigt dir immer den aktuellen Branch und den Status des Repositories an:

Nach source ~/.bashrc zeigt der neue Prompt den Pfad ~/git-tutorial/alice, in eckigen Klammern main mit einem grünen Haken und darunter die Uhrzeit.

Links steht, ob der letzte Befehl geklappt hat (✔), dann das Verzeichnis und in eckigen Klammern der Branch und der Zustand: ✔ heißt, es gibt nichts zu committen. Darunter folgen die Uhrzeit und das $, hinter dem du tippst.

5. Gemeinsam arbeiten

Ein Remote anlegen

In einem echten Projekt arbeitest du wahrscheinlich nicht alleine, sondern im Team. Damit mehrere Personen an demselben Repository arbeiten können, verwenden wir sogenannte »Remotes«. Ein Remote ist eine Kopie deines Repositories, die an einer anderen Stelle – also normalerweise auf einem anderen Computer oder Server – gespeichert ist.

In diesem Tutorial werden wir jedoch anstelle eines Servers einfach ein lokales Verzeichnis verwenden, um das Konzept der Remotes zu demonstrieren – die Handhabung ist in beiden Fällen identisch.

Zuerst legen wir ein neues Verzeichnis namens remote-repo an, das unser Remote-Repository darstellen soll:

cd ~/git-tutorial
mkdir remote-repo
cd remote-repo

In diesem Verzeichnis initialisieren wir ein neues, leeres Git-Repository:

git init --bare

Im Terminal sollte jetzt Folgendes zu sehen sein:

In ~/git-tutorial legen mkdir remote-repo, cd remote-repo und git init --bare ein leeres Repository an.

Ein »bare«-Repository ist ein spezieller Typ von Git-Repository, das kein Arbeitsverzeichnis enthält. Es wird hauptsächlich als zentrales Repository verwendet, auf das mehrere Entwickler zugreifen können, um ihre Änderungen zu pushen und zu pullen. In einem bare-Repository gibt es keine ausgecheckten Dateien, sondern nur die Git-Objekte und Metadaten.

Mit dem Befehl ls -l kannst du sehen, dass in diesem Verzeichnis die üblichen Git-Ordner und -Dateien vorhanden sind, die sonst im .git-Verzeichnis eines normalen Repositories zu finden sind:

ls -l im bare Repository zeigt branches, config, description, HEAD, hooks, info, objects und refs.

Wir wechseln jetzt zurück in das Verzeichnis von Alice, um unser Remote-Repository zu verknüpfen:

cd ~/git-tutorial/alice
git remote add origin ../remote-repo

Der Befehl bewirkt nur, dass das Remote-Repository verknüpft wird, es gibt keine Ausgabe im Terminal. Ein Blick in die Datei .git/config (cat .git/config) zeigt jedoch, dass das Remote-Repository jetzt konfiguriert ist:

Nach git remote add origin ../remote-repo zeigt cat .git/config einen Abschnitt [remote "origin"] mit url = ../remote-repo.

Der Befehl git remote add fügt ein neues Remote-Repository hinzu. In diesem Fall nennen wir das Remote origin, was der Standardname für das Haupt-Remote ist (wir könnten aber auch jeden anderen Namen wählen). Der Pfad ../remote-repo gibt den Speicherort des Remote-Repositories an. In einem echten Szenario würde hier normalerweise eine URL zu einem entfernten Server stehen, die z. B. mit https:// oder git@ beginnt.

Jetzt können wir unsere Änderungen an das Remote-Repository pushen. Verwende den folgenden Befehl, um den main-Branch zum ersten Mal zu pushen:

git push -u origin main

git push -u origin main überträgt 17 Objekte nach ../remote-repo, legt den Branch main an und meldet branch »main« set up to track »origin/main«.

Die Option -u (oder --set-upstream) richtet eine Tracking-Verbindung zwischen dem lokalen main-Branch und dem Remote-Branch origin/main ein. Dadurch kannst du in Zukunft einfach git push oder git pull ausführen, ohne den Remote-Namen und den Branch-Namen angeben zu müssen (ansonsten müsstest du immer git push origin main schreiben).

Aufgabe 7Alice pusht

Verknüpfe Alices Repository mit remote-repo und pushe main.

Bob klont das Repository

Um zu demonstrieren, wie mehrere Personen an demselben Repository arbeiten können, erstellen wir jetzt ein neues Verzeichnis für eine fiktive Person namens Bob:

cd ~/git-tutorial
mkdir bob
cd bob

Bob kann jetzt das Remote-Repository klonen, das wir zuvor erstellt haben:

git clone ../remote-repo

Das Repository wird in ein neues Verzeichnis namens remote-repo geklont. Wechsle in dieses Verzeichnis:

cd remote-repo

In ~/git-tutorial/bob klont git clone ../remote-repo das Repository, danach wechselt cd remote-repo hinein; der Prompt zeigt main.

Bob hat jetzt eine vollständige Kopie des Repositories, einschließlich der gesamten Versionsgeschichte. Öffne die Datei README.md mit dem Editor nano und ergänze die letzte Zeile, sodass dort :) (Bob was here) steht:

nano README.md

README.md in nano, die letzte Zeile lautet jetzt »:) (Bob was here)«.

Speichere die Datei mit StrgO und Enter und schließe den Editor mit StrgX. Mit git diff kann Bob seine Änderungen überprüfen:

git diff zeigt in Bobs Kopie die Zeile :) in Rot entfernt und »:) (Bob was here)« in Grün hinzugefügt.

Wir haben nun drei Möglichkeiten, wie Bob seine Änderungen committen kann:

  1. git add README.md und git commit -m "Bob was here"
  2. git commit -a -m "Bob was here"
  3. git commit -a – bei dieser Variante öffnet sich ein Texteditor, in dem Bob seine Commit-Nachricht eingeben kann.

git commit -a -m "Bob was here", 1 file changed, 1 insertion, 1 deletion; der Prompt zeigt einen Commit Vorsprung.

Achtung: Die Änderungen liegen immer noch komplett bei Bob lokal vor. Der Objektgraph in Bobs Repository ist gewachsen, aber das Remote-Repository hat davon noch nichts mitbekommen.

Um die Änderungen an das Remote-Repository zu senden, verwendet Bob den Befehl git push:

git push überträgt Bobs Commit, main -> main.

Alice holt Bobs Änderung

Wechsle nun wieder in das Verzeichnis von Alice:

cd ~/git-tutorial/alice

Mit dem Befehl git pull kann Alice die Änderungen von Bob in ihr lokales Repository holen und mit ihrem aktuellen Stand zusammenführen:

In Alices Verzeichnis holt git pull Bobs Commit, Fast-forward, README.md mit 1 insertion und 1 deletion.

Alice sieht mit cat README.md, dass die Datei aktualisiert wurde und jetzt auch Bobs Änderung enthält:

cat README.md zeigt bei Alice jetzt auch die Zeile »:) (Bob was here)«.

Merge-Konflikte

Wenn Alice und Bob gleichzeitig an derselben Datei arbeiten und ihre Änderungen nicht kompatibel sind, entsteht ein Merge-Konflikt. Um dies zu demonstrieren, ändern wir die Datei README.md sowohl bei Alice als auch bei Bob.

Alice ändert die Datei README.md:

echo "HEYYYY" >> README.md
Vorhin haben wir > verwendet, um eine Ausgabe in eine Datei umzuleiten. Das >> fügt die Ausgabe am Ende der Datei hinzu, anstatt den Inhalt zu überschreiben. Achte also darauf, dass du >> verwendest, um die Zeile hinzuzufügen, ohne den bestehenden Inhalt zu löschen.

Überprüfe die Änderungen mit cat und git diff:

Alice hängt HEYYYY an README.md an, cat zeigt die Datei, git diff die neue Zeile +HEYYYY.

Alice committet ihre Änderungen und pusht sie zum Remote-Repository:

git commit -a -m "HEYYYY"
git push

Alice committet mit git commit -a -m "HEYYYY" und schickt den Commit mit git push ins Remote-Repository.

Wechsle jetzt in Bobs Verzeichnis:

cd ~/git-tutorial/bob/remote-repo

Bob ändert ebenfalls die Datei README.md:

echo "HOLAAA" >> README.md

Er überprüft seine Änderungen mit git diff und committet sie:

git diff
git commit -a -m "HOLAAA"

Bob hängt HOLAAA an README.md an, git diff zeigt +HOLAAA, und er committet mit git commit -a -m "HOLAAA".

Wenn Bob jetzt versucht, seine Änderungen zu pushen, erhält er eine Fehlermeldung, da Alice bereits Änderungen gepusht hat, die Bob noch nicht in seinem lokalen Repository hat:

git push bei Bob wird abgelehnt, ! [rejected] main -> main (fetch first), mit dem Hinweis, erst git pull auszuführen.

Um die Änderungen von Alice zu holen und mit seinen eigenen zusammenzuführen, führt Bob den Befehl git pull aus:

git pull bei Bob meldet »You have divergent branches« mit drei Möglichkeiten für git config pull.rebase und bricht mit fatal ab.

Auch hier erhält Bob eine Fehlermeldung: Sein Branch und der von Alice haben sich auseinanderentwickelt (»divergent branches«), weil beide gleichzeitig an derselben Datei gearbeitet haben. Git möchte wissen, wie es die beiden Stände in Zukunft zusammenführen soll, und bietet drei Strategien an, von denen wir eine als Voreinstellung für unser lokales Repository wählen sollen:

  1. git config pull.rebase false – Standard-Merge-Strategie
  2. git config pull.rebase true – Rebase-Strategie
  3. git config pull.ff only – Nur Fast-Forward-Merges
Welche Strategie du wählst, hängt von deinem Workflow und deinen Präferenzen ab. Die Standard-Merge-Strategie ist einfach zu verstehen und zu verwenden, während die Rebase-Strategie eine sauberere Historie erzeugt. Die Fast-Forward-Strategie ist nützlich, wenn du eine lineare Historie bevorzugst und keine Merge-Commits erstellen möchtest.

Bob entscheidet sich für die Standard-Merge-Strategie und führt die folgenden Befehle aus:

git config pull.rebase false
git pull

Nach git config pull.rebase false meldet git pull CONFLICT (content) Merge conflict in README.md; der Prompt zeigt MERGING.

Wir sehen, dass Git den Merge-Konflikt in der Datei README.md markiert hat. Bob muss die Datei jetzt manuell bearbeiten, um den Konflikt zu lösen. Öffne die Datei README.md in einem Texteditor (nano README.md):

README.md in nano mit den Konfliktmarkierungen, zwischen <<<<<<< HEAD und ======= steht HOLAAA, darunter bis >>>>>>> HEYYYY.

Aufgabe 8Konfliktmarkierungen

Bob sieht nach git pull diese Stelle in README.md. Was steht zwischen <<<<<<< HEAD und =======?

<<<<<<< HEAD

HOLAAA
=======
HEYYYY
>>>>>>> ef91739
Konfliktmarkierungen

Hier siehst du die Konfliktmarkierungen:

<<<<<<< HEAD
HOLAAA
=======
HEYYYY
>>>>>>> ef91739…

Die Markierungen <<<<<<<, ======= und >>>>>>> zeigen die Bereiche an, in denen der Konflikt aufgetreten ist. Der obere Abschnitt enthält Bobs lokale Änderungen, während der untere Abschnitt die Änderungen des Remote-Repository (also Alices Änderungen) enthält. Hinter >>>>>>> steht die ID von Alices Commit.

Bob muss sich jetzt entscheiden, wie er den Konflikt lösen möchte. Er könnte zum Beispiel beide Änderungen durch eine dritte ersetzen und die drei Markierungszeilen löschen, sodass in der Datei Folgendes steht:

Hello, planet!

Git is awesome!

:) (Bob was here)
BONJOUR!
Ganze Zeilen löschst du in nano am einfachsten mit Entf oder der Rücktaste. Die Tastenkombination StrgK, mit der nano sonst eine Zeile ausschneidet, fängt im Workspace Visual Studio Code ab. Du kannst die Datei auch im Editor von Visual Studio Code öffnen und dort bearbeiten.

README.md in nano ohne Konfliktmarkierungen, die letzte Zeile lautet BONJOUR!.

Speichere die Datei und beende den Editor. Bob muss den Konflikt nun als gelöst markieren, indem er die Datei mit git add zur Staging Area hinzufügt. Anschließend kann er den Merge-Commit, der durch den Pull-Vorgang erstellt wurde und zunächst aufgrund des Konflikts unvollständig war, abschließen und danach seine Änderungen pushen:

git add README.md
git commit -m "fixed merge conflict"
git push

git add README.md, git commit -m "fixed merge conflict" und git push, der Prompt zeigt danach main mit grünem Haken.

Alice hat von dem Merge-Konflikt nichts mitbekommen, da Bob den Konflikt lokal gelöst hat, bevor er seine Änderungen gepusht hat. Wenn Alice jetzt ihre Änderungen mit git pull holt, erhält sie die von Bob aktualisierte Datei README.md.

Aufgabe 9Bob hat aufgelöst

Löse Bobs Konflikt, schließe den Merge ab und pushe.

Alice holt mit git pull Bobs Lösung des Konflikts, Fast-forward, README.md mit 1 insertion und 1 deletion.

6. Nützlich für später

Tags

Tags sind eine Möglichkeit, bestimmte Punkte in der Versionsgeschichte zu markieren, z.B. für Releases oder wichtige Meilensteine. Wir erstellen jetzt einen Tag namens v1.0 für den aktuellen Commit im main-Branch:

git tag -a v1.0 -m "v1.0 release"
Der Befehl git tag -a erstellt einen annotierten Tag. Annotierte Tags enthalten zusätzliche Informationen wie den Tag-Namen, die Commit-ID, den Autor und eine Nachricht. Dies ist nützlich, um wichtige Punkte in der Versionsgeschichte zu markieren.

Mit git log --oneline --graph siehst du die ganze Geschichte auf einen Blick, mit dem Tag und den Verzweigungen, die Alice und Bob hinterlassen haben:

git log --oneline --graph zeigt die ganze Geschichte als Graph, oben »fixed merge conflict« mit HEAD -> main, tag v1.0 und origin/main, darunter die Zweige HEYYYY und HOLAAA, Bob was here, den Merge von feature und ganz unten initial commit.

Aufgabe 10Tags pushen

Du hast git tag -a v1.0 -m "v1.0 release" und dann git push ausgeführt. Ist der Tag auf dem Remote?

Tags pushen

So können wir später leicht zu diesem Punkt in der Geschichte zurückkehren, wenn wir z.B. ein Release erstellen möchten. Um Tags zu pushen, verwenden wir den Befehl git push mit dem Tag-Namen:

git push origin v1.0

…oder um alle Tags auf einmal zu pushen:

git push origin --tags
git push überträgt den aktuellen Branch, Tags aber nicht von selbst: Sie muss man ausdrücklich pushen.

Zu einem Stand springen

Um zu einem bestimmten Branch oder Tag zurückzukehren, verwenden wir den Befehl git checkout. Du kannst also z. B. folgende Befehle verwenden:

  • git checkout main – wechselt zum main-Branch
  • git checkout v1.0 – wechselt zum Tag v1.0
  • git checkout HEAD~2 – wechselt zu dem Commit, der zwei Commits vor dem aktuellen HEAD liegt
Beachte, dass das Auschecken eines Tags oder eines älteren Commits dazu führt, dass du dich in einem »detached HEAD«-Zustand befindest. In diesem Zustand kannst du keine neuen Commits zu einem Branch hinzufügen, da HEAD nicht auf einen Branch zeigt. Wenn du Änderungen vornehmen möchtest, solltest du stattdessen von dieser Stelle aus einen neuen Branch erstellen.

Fehler rückgängig machen

Manchmal möchtest du vielleicht Änderungen an Dateien rückgängig machen. Welcher Befehl passt, hängt davon ab, wie weit die Änderung schon gekommen ist:

Die Änderung ist … Befehl Was passiert
nur im Arbeitsverzeichnis git restore README.md Die Datei ist wieder auf dem Stand des letzten Commits. Die Änderung ist weg.
mit git add bereitgelegt git restore --staged README.md Die Änderung kommt aus der Staging Area zurück ins Arbeitsverzeichnis; verloren geht nichts. Danach geht es weiter wie in der Zeile darüber.
schon committet git revert HEAD Ein neuer Commit hebt den letzten auf.
Denke daran, dass Knoten im Git-Objektgraphen niemals gelöscht werden. Selbst wenn du einen Commit rückgängig machst, bleibt der ursprüngliche Commit weiterhin in der Versionsgeschichte erhalten – es wird nur ein neuer Commit erstellt, der die Änderungen des ursprünglichen Commits aufhebt.
Aufgabe 11Ganz zurück

Du hast README.md geändert und mit git add bereitgelegt. Dann merkst du, die Änderung war Unsinn und soll ganz weg, so als hättest du die Datei nie angefasst. Was tust du?

Ganz zurück
Ausprobieren: Probier die Tabelle in alice aus, an einer Änderung, die du nicht brauchst. Sieh vorher mit git status nach, dass README.md dort nicht als geändert auftaucht, sonst verwirfst du am Ende mehr als nur den Versuch. Häng mit echo "Eins" >> README.md eine Zeile an (>> hängt an, statt zu überschreiben), lege sie mit git add README.md bereit und häng mit echo "Zwei" >> README.md noch eine an. Wie oft steht README.md jetzt in git status? Was zeigt git diff, und was git diff --staged? Mach danach beides rückgängig, mit den Befehlen aus der Tabelle, und sieh nach jedem Schritt mit git status und cat README.md nach, wo die beiden Zeilen gerade sind.

7. Zusammenfassung

Alleine git init, git status, git add, git commit -m, git commit -a, git mv, git diff, git log
Zeitreise git checkout HEAD~2, git checkout main, git restore, git revert
Branches git branch, git checkout, git merge, git branch -d, .gitignore
Gemeinsam git init --bare, git remote add, git push -u, git clone, git pull, git config pull.rebase false
Markieren git tag -a, git push origin --tags

Mit diesem Wissen kannst du Git für deine eigenen Projekte nutzen. Zum Nachschlagen stehen diese Befehle und ein paar weitere im Handbuch unter Git: Versionen verwalten. Wie du mit einem Git-Server wie GitLab, Gitea oder GitHub arbeitest, steht im Handbuch unter Git-Server: Zugang und Veröffentlichen.

Cover von »Pro Git«, zweite Auflage: der Titel in Orange auf Schwarz, darunter Scott Chacon und Ben Straub.

Pro Git, Scott Chacon und Ben Straub, Apress, 2. Auflage 2014. Alles, was hier fehlt: Rebase, Git auf einem Server, die Arbeit mit GitHub und wie Git seine Commits intern speichert. Frei lesbar, auch auf Deutsch: auf git-scm.com.

Cover: Apress, CC BY-NC-SA 3.0 (progit2)