Git ist schnell erklärt, aber nicht schnell verstanden. Genau da liegt meistens das Problem. Viele kennen ein paar Befehle, kommen im Alltag auch irgendwie durch und nutzen Git trotzdem eher wie eine Fernbedienung mit zu vielen Tasten. Das funktioniert, bis etwas schiefläuft. Dann wird aus git pull plötzlich Stress, aus einem Merge Konflikt ein Ratespiel und aus einem falschen Commit eine halbe Stunde Unsicherheit.

Wenn du Git wirklich sauber nutzen willst, brauchst du nicht einfach mehr Befehle. Du brauchst ein klares Bild davon, was Git intern speichert und was ein Befehl tatsächlich verändert. Ab diesem Punkt fühlt sich Git nicht mehr zufällig an, sondern berechenbar. Genau darum geht es hier.

Die Grundlagen wie Repository, Commit, Branch und Remote setze ich in diesem Artikel voraus. Jetzt schauen wir auf das, was im Alltag wirklich hilft: den Unterschied zwischen Arbeitsverzeichnis, Staging Area und Commit Historie, die Rolle von HEAD, den echten Sinn von Branches und warum Befehle wie reset, restore, rebase oder cherry-pick gar nicht so mysteriös sind, wie sie am Anfang wirken.

 

Git speichert keine Ordner, sondern Zustände

Der wichtigste Schritt ist dieser: Git denkt nicht zuerst in Dateien, sondern in Zuständen. Ein Commit ist kein loses Paket mit ein paar Änderungen, sondern ein vollständiger Schnappschuss deines Projekts zu einem bestimmten Zeitpunkt. Intern speichert Git das sehr effizient, aber für dein Verständnis hilft genau dieses Bild.

Wenn du in einem Maven Projekt eine Klasse änderst, etwa in src/main/java, dann bearbeitest du erst einmal nur dein Arbeitsverzeichnis. Git weiß in diesem Moment noch nicht, dass diese Änderung Teil deines nächsten Commits werden soll. Erst mit git add schiebst du den aktuellen Stand der Datei in die Staging Area. Diese Staging Area ist im Alltag extrem wichtig, weil du dort festlegst, was wirklich in den nächsten Commit kommt.

git add src/main/java/de/beispiel/service/UserService.java

Dieser Befehl markiert nicht einfach nur eine Datei als geändert. Er übernimmt den aktuellen Inhalt dieser Datei in die Staging Area. Wenn du danach weiter an derselben Datei arbeitest, kann dein Arbeitsverzeichnis schon wieder neuer sein als das, was du gerade gestaged hast. Genau deshalb wirkt Git auf viele am Anfang verwirrend. Es gibt nicht nur einen Zustand deiner Datei, sondern im Zweifel drei: den letzten Commit, die Version in der Staging Area und die Version in deinem Arbeitsverzeichnis.

git status zeigt dir genau diese Unterschiede.

git status

Wenn dort steht, dass etwas "Changes to be committed" ist, dann liegt diese Änderung in der Staging Area. Wenn dort zusätzlich "Changes not staged for commit" auftaucht, dann ist deine Datei nach git add noch einmal verändert worden. Das ist kein Fehler, sondern normales Git Verhalten.

Auch git diff wird in diesem Zusammenhang oft falsch verstanden. Ohne Zusatz vergleicht es dein Arbeitsverzeichnis mit der Staging Area.

git diff

Mit --staged vergleichst du die Staging Area mit dem letzten Commit.

git diff --staged

Wenn du diese zwei Vergleiche verinnerlichst, verstehst du plötzlich deutlich besser, was Git dir sagen will. Dann ist ein Commit nicht mehr "alles, was gerade offen ist", sondern genau das, was gerade in der Staging Area liegt.

 

Branches sind nur Zeiger und HEAD zeigt dir, wo du stehst

Viele stellen sich einen Branch wie einen getrennten Arbeitsbereich vor. Praktisch fühlt sich das auch so an, technisch ist ein Branch aber erst einmal nur ein Zeiger auf einen Commit. Mehr nicht. Wenn du einen neuen Commit erstellst, wird dieser Zeiger weitergeschoben.

HEAD ist wiederum der Zeiger auf den Branch, auf dem du gerade arbeitest. Meistens zeigt HEAD also nicht direkt auf einen Commit, sondern auf einen Branch Namen wie main oder feature/login.

Mit diesem Modell werden viele Befehle sofort logischer. Ein neuer Branch dupliziert nicht dein Projekt. Er setzt nur einen neuen Zeiger auf den aktuellen Commit.

git switch -c feature/login

Dieser Befehl erstellt den Branch feature/login auf Basis deines aktuellen Commits und wechselt direkt darauf. Wenn du danach committest, bewegt sich nur dieser Branch weiter. main bleibt auf seinem bisherigen Commit stehen.

Das kannst du dir mit git log gut ansehen.

git log --oneline --graph --decorate

--oneline zeigt jeden Commit kurz an. --graph malt die Commit Struktur dazu. --decorate blendet Branch Namen und HEAD ein. Gerade am Anfang ist das einer der wertvollsten Befehle überhaupt, weil er dir zeigt, wie Git deine Historie wirklich sieht.

Ein Merge ist dann im Kern nichts anderes als das Zusammenführen zweier Entwicklungslinien. Git sucht den gemeinsamen Ausgangspunkt und versucht, die Änderungen beider Seiten zusammenzubringen. Wenn dieselbe Stelle unterschiedlich geändert wurde, entsteht ein Konflikt. Git ist dann nicht kaputt. Git sagt dir nur ehrlich: Ich kann hier nicht sicher entscheiden.

Rebase wirkt anfangs komplizierter, ist aber mit demselben Modell gut greifbar. Beim Rebase werden deine Commits so neu aufgebaut, als wären sie auf einem anderen Commit entstanden.

git rebase main

Wenn du dich auf einem Feature Branch befindest, nimmt Git dabei deine Commits von diesem Branch und setzt sie nacheinander auf den aktuellen Stand von main. Der Vorteil ist eine lineare Historie. Der Nachteil ist, dass sich Commit IDs ändern, weil technisch neue Commits erzeugt werden.

Darum gilt eine einfache Regel: Rebase ist stark, solange du verstehst, dass du Historie umschreibst. Auf deinem lokalen Branch ist das oft sinnvoll. Auf gemeinsam genutzten Branches musst du damit vorsichtig sein.

 

Die Befehle, bei denen viele unsicher werden

Die meisten Probleme in Git entstehen nicht durch exotische Funktionen, sondern durch ein unsauberes Verständnis von ein paar Standardbefehlen. Besonders oft betrifft das restore, reset und cherry-pick.

git restore ist dafür da, Inhalte gezielt zurückzusetzen. Ohne weitere Optionen holst du eine Datei aus der Staging Area nicht zurück, sondern stellst den Stand im Arbeitsverzeichnis wieder her.

git restore src/main/resources/application.properties

Damit verwirfst du lokale Änderungen in genau dieser Datei und holst den Stand aus der aktuellen Git Sicht zurück. Wenn du dagegen etwas aus der Staging Area wieder entfernen willst, nutzt du --staged.

git restore --staged pom.xml

Das ist praktisch das Gegenteil von git add für diese Datei. Die Änderung bleibt in deinem Arbeitsverzeichnis erhalten, liegt aber nicht mehr in der Staging Area.

git reset ist mächtiger, weil der Befehl je nach Variante an unterschiedlichen Stellen eingreift. Genau deshalb sollte man ihn nicht blind benutzen. Sehr hilfreich ist zuerst die harmlose Variante.

git reset --soft HEAD~1

Damit springt dein aktueller Branch einen Commit zurück. Die Änderungen aus diesem Commit bleiben aber komplett in der Staging Area erhalten. Du hast also keinen Inhalt verloren, sondern nur den Commit aufgelöst. Das ist nützlich, wenn deine Commit Message schlecht war oder du zwei kleine Commits sauber neu bauen willst.

Mit --mixed, das auch der Standard ist, springt Git ebenfalls zurück, räumt aber die Staging Area leer. Die Änderungen bleiben nur noch im Arbeitsverzeichnis. Mit --hard wird es ernst, weil Git dann Branch, Staging Area und Arbeitsverzeichnis auf den Ziel Commit setzt. Lokale Änderungen sind danach weg.

git reset --hard HEAD~1

Das ist kein Befehl für hektische Momente. Wenn du ihn nutzt, dann bewusst.

Sehr praktisch im Projektalltag ist auch git cherry-pick.

git cherry-pick a1b2c3d

Damit übernimmst du genau einen bestimmten Commit auf deinen aktuellen Branch. Stell dir vor, du hast in einem anderen Branch einen kleinen Bugfix gemacht, willst aber nicht den ganzen Branch mergen. Dann ist cherry-pick genau richtig. Git nimmt den Inhalt dieses einen Commits und erzeugt daraus auf deinem aktuellen Branch einen neuen Commit mit demselben Änderungsinhalt.

Gerade in Java Projekten ist das nützlich, wenn du etwa einen kleinen Fix an einer Konfiguration, einer Exception Behandlung oder an einer Service Klasse gezielt in einen Release Branch ziehen willst, ohne andere laufende Arbeiten mitzunehmen.

 

Fazit

Git wirkt professionell, wenn es im Alltag unsichtbar wird. Das passiert nicht, weil du möglichst viele Befehle auswendig kannst, sondern weil du das Datenmodell dahinter verstanden hast. Sobald du sauber zwischen Arbeitsverzeichnis, Staging Area und Commit Historie unterscheiden kannst, werden selbst unangenehme Situationen deutlich ruhiger. Dann liest du git status nicht mehr wie eine Warnung, sondern wie einen präzisen Zustandsbericht.

Für mich ist genau das der Punkt, an dem Git vom Werkzeug zum verlässlichen Teil deines Entwicklungsalltags wird. Du musst dann nicht raten, was ein Befehl vermutlich macht. Du kannst es vorher einschätzen. Und genau das ist der Unterschied zwischen Git benutzen und Git wirklich verstehen.

Wenn du das üben willst, dann nicht in einem echten Projekt unter Druck. Leg dir ein kleines Test Repository an, ändere eine Datei, stage nur einen Teil, committe, setze zurück, probiere restore, erstelle einen Branch, führe einen Rebase aus und schau dir nach jedem Schritt git status und git log --oneline --graph --decorate an. Genau dabei baut sich das Verständnis auf, das dir später in echten Projekten Zeit und Nerven spart.