Kontext wechseln, ohne Änderungen zu verlieren
Wer mitten in einer Aufgabe dringend den Branch wechseln muss, kennt das Dilemma zwischen halbfertigem Code und einem sauberen Arbeitsverzeichnis. git stash legt uncommittete Änderungen sicher beiseite, verwaltet mehrere benannte Stashes parallel, erlaubt partielles Sichern einzelner Hunks und unterscheidet klar zwischen pop und apply, damit unterbrochene Arbeit nie verloren geht.
Inhaltsverzeichnis
- 1. Der Stash-Mechanismus: Commits auf refs/stash statt Zwischenablage
- 2. Schneller Kontextwechsel mit git stash push
- 3. Benannte Stashes und die Stash-Liste verwalten
- 4. Partielles Stashen mit git stash push -p
- 5. git stash pop vs. git stash apply: Der entscheidende Unterschied
- 6. Untracked und ignorierte Dateien beim Stashen erfassen
- 7. Stash-Konflikte beim Pop verstehen und lösen
- 8. Stashes und Branches: lose Bindung, keine Beschränkung
- 9. git stash im Vergleich zu improvisierten Workarounds
- 10. Zusammenfassung
- 11. FAQ
1. Der Stash-Mechanismus: Commits auf refs/stash statt Zwischenablage
git stash wirkt nach außen wie eine Zwischenablage, ist intern aber nichts anderes als eine Reihe gewöhnlicher Commits. Beim ersten Aufruf legt Git die Referenz refs/stash an und schreibt darunter einen Commit, der bis zu drei Elternteile besitzt: den Commit, auf dem HEAD zum Zeitpunkt des Stashens stand, einen Commit, der den Zustand der Staging Area (Index) festhält, und einen weiteren, der den Zustand des Arbeitsverzeichnisses abbildet. Wird zusätzlich -u oder --include-untracked verwendet, kommt ein vierter Elternteil mit den bis dahin ungetrackten Dateien hinzu.
Jeder weitere git stash push erzeugt keinen neuen Branch, sondern einen weiteren Commit, dessen Vorgänger über die Reflog von refs/stash verkettet wird, genau wie bei einem normalen Branch-Reflog. Deshalb funktionieren vertraute Befehle wie git log, git show oder git diff direkt auf einem Stash-Eintrag, etwa git show stash@{0} oder git diff stash@{0}^1 stash@{0}^2, um Index- gegen Arbeitsverzeichnis-Änderungen zu vergleichen. Diese Commit-basierte Natur erklärt auch, warum ein Stash über git fsck theoretisch wiederherstellbar bleibt, selbst nachdem er aus der Liste entfernt wurde, solange die Garbage Collection ihn noch nicht eingesammelt hat.
# Ersten Stash anlegen und den internen Aufbau ansehen
git stash push -m "WIP: checkout refactor"
# refs/stash zeigt auf den neuesten Stash-Commit
git rev-parse refs/stash
# a1c92f4e8b3d0176f2e4a9c8b1d3e5f7a9c1e3d5
# Der Stash-Commit selbst, wie jeder andere Commit auch
git log --oneline -1 refs/stash
# a1c92f4 WIP on feature/checkout: 7f3e9a1 Refactor checkout service
git show --stat stash@{0}
# stash@{0}
# Merge: 7f3e9a1 9b2c410
# Author: M. Berger <m.berger@example.com>
# src/checkout/CartService.php | 12 ++++++------
# src/checkout/CartService.spec.js | 4 ++++
# Elternteile einzeln pruefen: Basis, Index, Arbeitsverzeichnis
git rev-list --parents -n 1 stash@{0}
# a1c92f4e 7f3e9a1d 9b2c4102
2. Schneller Kontextwechsel mit git stash push
Der häufigste Auslöser für git stash ist ein dringender Kontextwechsel mitten in der Arbeit: Ein Hotfix muss sofort auf einen anderen Branch, aber der aktuelle Stand ist weder fertig noch commit-würdig. git stash push (oder kurz git stash, was intern push ohne Optionen aufruft) verschiebt alle Änderungen an getrackten Dateien im Index und im Arbeitsverzeichnis in einen neuen Stash-Eintrag und setzt den Arbeitsbaum exakt auf den Stand von HEAD zurück, ohne einen Commit zu hinterlassen.
Der Branch-Wechsel selbst wird dadurch trivial, weil git checkout oder git switch keine Konflikte mehr mit uncommitteten Änderungen melden kann, das Arbeitsverzeichnis ist ja sauber. Nach Erledigung der Unterbrechung genügt ein Wechsel zurück auf den ursprünglichen Branch und git stash pop, um exakt den vorherigen Zustand von Index und Arbeitsverzeichnis wiederherzustellen. Dieser Zyklus aus push, wechseln, erledigen, zurückwechseln, pop lässt sich beliebig oft wiederholen und ist der Kernworkflow, für den stash überhaupt entworfen wurde.
# Mitten in der Arbeit, Hotfix muss sofort auf main
git status
# modified: src/checkout/CartService.php
# modified: src/checkout/CartService.spec.js
git stash push -m "WIP: checkout refactor before hotfix"
# Saved working directory and index state On feature/checkout: WIP: checkout refactor before hotfix
git switch main
git switch -c hotfix/cart-discount-rounding
# ... Hotfix schreiben, testen, committen, mergen ...
git switch feature/checkout
git stash pop
# On branch feature/checkout
# Changes not staged for commit:
# modified: src/checkout/CartService.php
# modified: src/checkout/CartService.spec.js
# Dropped refs/stash@{0} (a1c92f4e8b3d0176f2e4a9c8b1d3e5f7a9c1e3d5)
3. Benannte Stashes und die Stash-Liste verwalten
Sobald mehr als eine Baustelle gleichzeitig unterbrochen wird, reicht ein einzelner unbenannter Stash nicht mehr aus, um den Überblick zu behalten. git stash push -m "Beschreibung" versieht den neuen Eintrag mit einer aussagekräftigen Nachricht, die später in git stash list neben dem Index-Platzhalter stash@{0}, stash@{1} und so weiter erscheint. Ohne eigene Nachricht generiert Git automatisch eine Beschreibung aus Branch-Name und dem letzten Commit, etwa "WIP on feature/checkout: 7f3e9a1 Refactor checkout service", was bei mehreren Stashes auf demselben Branch schnell verwirrend wird.
Die Liste verhält sich wie ein Stack: Der zuletzt erstellte Stash bekommt immer den Index 0, ältere Einträge rutschen automatisch nach hinten. Das bedeutet, dass git stash pop ohne Argument immer den jüngsten Eintrag anwendet, während gezielt adressierte Einträge über git stash pop stash@{2} oder git stash apply stash@{1} unabhängig von ihrer Position bearbeitet werden können. git stash show stash@{n} liefert einen kompakten Diffstat, git stash show -p stash@{n} den vollständigen Patch, beides ohne den Stash tatsächlich anzuwenden oder ihn aus der Liste zu entfernen.
# Mehrere parallele Baustellen als benannte Stashes sichern
git stash push -m "WIP: checkout discount rounding fix"
git stash push -m "WIP: experiment with lazy-loaded cart totals"
git stash push -m "WIP: temporary debug logging, do not ship"
git stash list
# stash@{0}: On feature/cart-totals: WIP: temporary debug logging, do not ship
# stash@{1}: On feature/cart-totals: WIP: experiment with lazy-loaded cart totals
# stash@{2}: On feature/checkout: WIP: checkout discount rounding fix
# Diffstat eines bestimmten Stashes pruefen, ohne ihn anzuwenden
git stash show stash@{2}
# src/checkout/CartService.php | 12 ++++++------
# 1 file changed, 6 insertions(+), 6 deletions(-)
# Vollstaendigen Diff eines Stashes ansehen
git stash show -p stash@{1}
4. Partielles Stashen mit git stash push -p
Manchmal betrifft eine Änderung mehrere logisch unabhängige Dinge im selben Arbeitsverzeichnis, etwa einen echten Bugfix und temporären Debug-Code, der niemals gemerged werden soll. git stash push -p startet, analog zu git add -p, einen interaktiven Hunk-für-Hunk-Dialog: Für jeden zusammenhängenden Änderungsblock fragt Git, ob er in den Stash aufgenommen werden soll, mit den bekannten Optionen y (ja), n (nein), s (Hunk weiter aufteilen), e (manuell editieren) und q (Vorgang abbrechen).
Das Ergebnis ist ein Stash, der nur die ausgewählten Hunks enthält, während der Rest der Änderungen unverändert im Arbeitsverzeichnis stehen bleibt und sofort committet werden kann. Diese Technik ist besonders wertvoll, wenn ein Fix mitten in einer größeren, noch unfertigen Umbauarbeit entdeckt wird: Der Fix wird separiert, sofort gestasht, auf einem sauberen Branch angewendet und committet, während die eigentliche Umbauarbeit unterbrochen im Arbeitsverzeichnis liegen bleibt, ganz ohne einen Zwischen-Commit für halbfertigen Code zu erzeugen.
# Nur ausgewaehlte Hunks stashen, Rest bleibt im Arbeitsverzeichnis
git stash push -p -m "WIP: only the rounding fix"
# diff --git a/src/checkout/CartService.php b/src/checkout/CartService.php
# @@ -42,7 +42,7 @@ class CartService
# - return round($total, 2);
# + return round($total, 2, PHP_ROUND_HALF_EVEN);
#
# Stash this hunk [y,n,q,a,d,s,e,?]? y
#
# @@ -88,3 +88,6 @@ class CartService
# + // TODO: remove before merging, only for local debugging
# + error_log('cart total: ' . $total);
#
# Stash this hunk [y,n,q,a,d,s,e,?]? n
git status
# modified: src/checkout/CartService.php
# (nur der Debug-Log-Hunk bleibt sichtbar, der Rundungs-Fix ist gestasht)
5. git stash pop vs. git stash apply: Der entscheidende Unterschied
git stash pop und git stash apply erledigen auf den ersten Blick dasselbe: Sie wenden die im Stash gesicherten Änderungen auf das aktuelle Arbeitsverzeichnis an. Der Unterschied liegt im Nachgang. pop ist im Kern ein apply gefolgt von einem automatischen drop, der Eintrag verschwindet also nach erfolgreicher Anwendung aus der Stash-Liste. apply dagegen lässt den Eintrag unangetastet in der Liste stehen, selbst nach erfolgreicher Anwendung, und muss bei Bedarf explizit mit git stash drop stash@{n} entfernt werden.
Dieser Unterschied wird genau dann wichtig, wenn Konflikte drohen oder derselbe Stash mehrfach gebraucht wird. Bei einem riskanten pop, der mit einem Merge-Konflikt fehlschlägt, bleibt der Stash-Eintrag zur Sicherheit trotzdem in der Liste erhalten, Git löscht ihn in diesem Fall nicht automatisch, aber genau darauf sollte man sich nicht verlassen: Wer unsicher ist, testet grundsätzlich mit apply, prüft das Ergebnis in Ruhe, und entfernt den Eintrag erst danach manuell mit drop. Auch wer denselben Stash auf mehreren Branches ausprobieren will, etwa um zu vergleichen, wo er sauberer anwendbar ist, greift zu apply statt zu pop.
6. Untracked und ignorierte Dateien beim Stashen erfassen
Ein häufiger Stolperstein: git stash push erfasst standardmäßig nur Änderungen an bereits von Git getrackten Dateien, also alles, was im Index oder im Arbeitsverzeichnis zu einer bekannten Datei gehört. Komplett neue, noch nie hinzugefügte Dateien bleiben unangetastet im Arbeitsverzeichnis liegen, selbst wenn sie inhaltlich untrennbar zur gerade gestashten Änderung gehören, etwa eine neu angelegte Klasse, die von einer geänderten bestehenden Datei bereits referenziert wird. Nach dem Stashen und einem Branch-Wechsel führt das zu einem Build, der scheinbar grundlos fehlschlägt, weil die referenzierte neue Datei fehlt.
Die Flag -u beziehungsweise --include-untracked schließt neue, ungetrackte Dateien explizit in den Stash ein. Von .gitignore erfasste Dateien, etwa generierte Build-Artefakte oder lokale Konfigurationsdateien, werden selbst davon noch ausgenommen und benötigen zusätzlich -a beziehungsweise --all, was in der Praxis seltener sinnvoll ist, weil ignorierte Dateien meist bewusst außerhalb der Versionskontrolle gehalten werden. Als Faustregel gilt: Sobald ein Fix neue Dateien einschließt, ist -u nicht optional, sondern Voraussetzung für einen vollständigen, verlustfreien Stash.
# Neue, noch ungetrackte Dateien gehoeren standardmaessig NICHT zum Stash
git status
# modified: src/checkout/CartService.php
# Untracked files:
# src/checkout/RoundingStrategy.php
git stash push -u -m "WIP: rounding strategy incl. new file"
# Saved working directory and index state On feature/checkout: WIP: rounding strategy incl. new file
# (RoundingStrategy.php ist jetzt Teil des Stashes)
# Auch von .gitignore erfasste Dateien einschliessen, z.B. generierte Configs
git stash push -a -m "WIP: incl. ignored build artifacts"
# Einen bestimmten Stash gezielt auf einem ANDEREN Branch anwenden
git switch release/2.6
git stash apply stash@{2}
# stash@{2} bleibt dabei weiterhin in der Liste erhalten
git stash list
# stash@{0}: On feature/checkout: WIP: incl. ignored build artifacts
# stash@{1}: On feature/checkout: WIP: rounding strategy incl. new file
# stash@{2}: On feature/checkout: WIP: checkout discount rounding fix
7. Stash-Konflikte beim Pop verstehen und lösen
Ein Stash entsteht immer relativ zu einem bestimmten Basis-Commit. Ist der aktuelle Branch seit dem Stashen weitergewandert, etwa durch neue Commits desselben Branches oder einen zwischenzeitlichen Merge, kann git stash pop beim Anwenden auf denselben Zeilen wie der Stash-Patch aufsetzen und einen echten Merge-Konflikt erzeugen, mit den vertrauten Konfliktmarkierungen <<<<<<<, ======= und >>>>>>> direkt im Arbeitsverzeichnis. Wichtig dabei: Git entfernt den Stash-Eintrag aus Sicherheitsgründen nur dann aus der Liste, wenn die Anwendung vollständig ohne Konflikt gelungen ist, bei einem Konfliktabbruch bleibt er erhalten.
Nach einem Konflikt gibt es drei sinnvolle Wege: Die Konfliktmarkierungen manuell auflösen, die Dateien mit git add zum Index hinzufügen und den Stash-Eintrag anschließend bewusst per git stash drop entfernen, da er nach einem fehlgeschlagenen pop nicht automatisch gelöscht wird. Alternativ lässt sich der komplette Vorgang mit git reset --merge rückgängig machen, um erneut in Ruhe vorzugehen. Wer Konflikte grundsätzlich vermeiden will, wendet den Stash zunächst probeweise mit apply auf einem frischen, sauberen Branch an, bevor er ihn auf dem eigentlichen Zielbranch tatsächlich poppt.
8. Stashes und Branches: lose Bindung, keine Beschränkung
Ein oft übersehenes Detail: Ein Stash-Eintrag ist keinem Branch fest zugeordnet. Er wird global unter refs/stash im Repository gespeichert, unabhängig davon, auf welchem Branch er erstellt wurde. Die Nachricht "On feature/checkout: ..." ist reine Information zur Herkunft, keine technische Einschränkung. Das bedeutet, ein auf feature/checkout erstellter Stash lässt sich problemlos auf main, auf einem Release-Branch oder einem völlig unabhängigen Feature-Branch anwenden, solange der Inhalt inhaltlich dorthin passt.
Je weiter sich Ziel-Branch und ursprünglicher Basis-Commit des Stashes auseinanderentwickelt haben, desto wahrscheinlicher werden allerdings Konflikte beim Anwenden. Für genau diesen Fall bietet Git git stash branch <neuer-branch-name> [<stash>]: Der Befehl legt einen neuen Branch exakt am Basis-Commit des Stashes an, checkt ihn aus und wendet den Stash dort an, wo er garantiert konfliktfrei passt, weil der Branch ja genau von diesem Ausgangspunkt abzweigt. Bei Erfolg wird der Stash-Eintrag automatisch aus der Liste entfernt, was diesen Befehl zur robustesten Methode macht, einen alten, divergierten Stash überhaupt noch sauber zu retten.
9. git stash im Vergleich zu improvisierten Workarounds
Viele der Probleme rund um verlorene, unvollständige oder schwer nachvollziehbare Stashes entstehen nicht durch git stash selbst, sondern durch improvisierte oder unvollständige Nutzung des Befehls. Wer etwa konsequent unbenannte Stashes anlegt, untracked Dateien vergisst oder ganze Arbeitsverzeichnisse stasht, obwohl nur ein Teil relevant ist, produziert genau die Verwirrung, die stash eigentlich vermeiden soll. Die folgende Übersicht stellt fünf typische Situationen gegenüber: den riskanten, unvollständigen Ansatz auf der einen Seite und den robusten, empfohlenen stash-Ansatz auf der anderen.
| Situation | Riskanter Ansatz | Empfohlener stash-Ansatz |
|---|---|---|
| Neue Dateien gehören zum Fix | git stash ohne Flags, verliert untracked Dateien | git stash push -u |
| Stash später erneut brauchen | git stash pop, entfernt aus der Liste, Konfliktrisiko | git stash apply für branchübergreifende Wiederverwendung |
| Nur ein Teil ist relevant | Gesamtes Arbeitsverzeichnis auf einmal stashen | git stash push -p für partielles Stashen |
| Mehrere parallele Stashes | Unbenannter Stash, später kaum identifizierbar | git stash push -m "Beschreibung" |
| Änderungen "in Sicherheit bringen" | Dateien manuell in ein temporäres Verzeichnis kopieren | git stash als sichere, atomare Operation |
In jeder Zeile zeigt sich dasselbe Muster: Der riskante Ansatz verlässt sich auf Zufall oder manuelle Disziplin, während der empfohlene stash-Ansatz die eingebauten Sicherungen von Git nutzt, benannte, adressierbare Einträge, vollständige Erfassung inklusive neuer Dateien, gezielte Teilauswahl und eine klare Trennung zwischen einmaliger Anwendung und dauerhafter Wiederverwendung.
Mironsoft
Magento- und Hyvä-Entwicklung mit sauberen Git-Workflows
Verlorene Änderungen im Magento-Projekt vermeiden?
Wir etablieren für euer Magento- und Hyvä-Team klare Git-Konventionen, mit denen Kontextwechsel, parallele Stashes und branchübergreifende Arbeit sicher und nachvollziehbar bleiben.
Git-Workflow-Audit
Branching-, Merge- und stash-taugliche Team-Konventionen
Team-Onboarding
Praxisnahe Git-Schulungen für Entwicklerteams
Incident-Support
Schnelle Hilfe bei verlorenen oder verworrenen Stashes
10. Zusammenfassung
git stash löst ein Problem, das in jedem aktiven Repository ständig auftritt: unterbrochene, noch nicht commit-würdige Arbeit sicher beiseitelegen, ohne sie zu verlieren oder einen unsauberen Zwischen-Commit zu erzeugen. Technisch ist ein Stash nichts Exotisches, sondern eine Reihe gewöhnlicher Commits unter refs/stash mit bis zu vier Elternteilen für Basis, Index, Arbeitsverzeichnis und optional untracked Dateien. Der Kern-Workflow aus git stash push, Branch wechseln, Arbeit erledigen, zurückwechseln und git stash pop deckt den Großteil aller Anwendungsfälle ab, während benannte Stashes mit -m und git stash list mehrere parallele Baustellen übersichtlich halten.
Drei Details verdienen besondere Aufmerksamkeit: Erstens erfasst git stash push standardmäßig keine neuen, ungetrackten Dateien, wofür -u zwingend nötig ist. Zweitens ist der Unterschied zwischen pop, das den Eintrag entfernt, und apply, das ihn zur Wiederverwendung behält, entscheidend bei Konfliktrisiko oder branchübergreifender Nutzung. Und drittens sind Stashes nicht an ihren Ursprungsbranch gebunden, git stash branch schafft bei stark divergierten Zielbranches den saubersten Weg, einen alten Stash noch konfliktfrei zu retten.
git stash - Das Wichtigste auf einen Blick
Commits statt Zwischenablage
Stash ist auf refs/stash implementiert, mit bis zu vier Eltern-Commits für Basis, Index, Arbeitsverzeichnis und untracked Dateien.
Kontextwechsel
git stash push sichert Änderungen, Branch wechseln, dann git stash pop stellt den Zustand wieder her.
pop vs. apply
pop entfernt den Eintrag und birgt Konfliktrisiko, apply behält ihn für Wiederverwendung.
Typische Stolperfallen
-u für untracked, -a für ignorierte Dateien, git stash branch bei stark divergierten Branches.