Zum Inhalt springen
Alle Artikel
Lesen. Lernen. Anwenden.

Das Git Cheat Sheet für Fortgeschrittene

9. September 202614 Min. LesezeitDeutsch

Dieser Artikel wurde mit ChatGPT automatisch aus dem Englischen ins Deutsche übersetzt.

TLDR; Lass dir das Git Cheat Sheet für Fortgeschrittene als und per E-Mail schicken. Nutze es als Nachschlagewerk für Stash, Rebase, Cherry-Pick, Tags, Bisect, Reflog, Worktrees, Submodule und die sichere Wiederherstellung der Historie. Die ausführlichen Beispiele findest du weiter unten.

Das Git Cheat Sheet für Fortgeschrittene herunterladen

Vorschau des Git Cheat Sheets für Fortgeschrittene

Fordere das vollständige PDF zum Ausdrucken und das PNG in voller Auflösung an. Wir schicken beide Dateien direkt in dein Postfach.

Was solltest du vor dem Start wissen?

Dieses Cheat Sheet setzt das ultimative Git Cheat Sheet fort. Lies zuerst diese Einführung, wenn Repositories, Commits, Branches, Merges oder Remotes noch neu für dich sind. Alle folgenden Beispiele verwenden dasselbe Projekt the-ultimate-git-cheat-sheet auf GitHub.

Klone das Projekt einmal, bevor du beginnst. Jeder Abschnitt erstellt seinen eigenen Tutorial-Branch sowie die nötigen Dateien und Commits. Du musst also keinen fehlenden Ausgangszustand selbst erfinden.

bash
$ git clone https://github.com/aichbauer/the-ultimate-git-cheat-sheet.git
$ cd the-ultimate-git-cheat-sheet
# short version: git status -s
$ git status --short
## => output is empty because the working tree is clean

Die Ausgaben und kurzen Commit-Hashes dienen der Veranschaulichung. Deine Hashes, Autorenangaben und Datumswerte werden abweichen.

In den Beispielen prüfen wir Änderungen vor dem Commit. git diff zeigt noch nicht vorgemerkte Änderungen an erfassten Dateien. Nach git add prüfst du mit git diff --staged die Version im Staging-Bereich, einschließlich des vollständigen Inhalts neu hinzugefügter Dateien.

Was ist Git Stash?

Ein Stash speichert noch nicht committete Änderungen vorübergehend und versetzt dein Arbeitsverzeichnis wieder in einen sauberen Zustand. Das hilft, wenn du den Branch wechseln musst, bevor deine Arbeit bereit für einen sinnvollen Commit ist.

Ein Stash ersetzt keine Commits. Stashes sind lokal, leicht zu vergessen und werden beim Push nicht geteilt.

Wie speicherst und prüfst du einen Stash?

bash
# create an isolated branch and a tracked change to stash
# short version: git switch -c tutorial/stash main
$ git switch --create tutorial/stash main
$ printf '\nDocument remote-tracking branches here.\n' >> branches.md

# inspect the changed path and its unstaged patch before storing it
$ git status --short
## => output
###  M branches.md
$ git diff -- branches.md

# store the tracked change with a useful description
# short version: git stash push -m "Draft remote notes"
$ git stash push --message "Draft remote notes"
## => output
### Saved working directory and index state On tutorial/stash: Draft remote notes
$ git status --short
## => output is empty because the working tree is clean

# create another tracked change and a new, untracked file
$ printf '\nSee tags.md for release tags.\n' >> README.md
$ printf '# Release Tags\n' > tags.md

# status includes both paths, while diff shows the tracked change
$ git status --short
## => output
###  M README.md
### ?? tags.md
$ git diff -- README.md

# include the untracked file in this second stash
# short version: git stash push -u -m "Draft tags page"
$ git stash push --include-untracked --message "Draft tags page"
## => output
### Saved working directory and index state On tutorial/stash: Draft tags page
$ git status --short
## => output is empty because the working tree is clean

# list saved stashes
$ git stash list
## => output
### stash@{0}: On tutorial/stash: Draft tags page
### stash@{1}: On tutorial/stash: Draft remote notes

# inspect tracked and untracked changes in the newest stash
# short version: git stash show -u -p stash@{0}
$ git stash show --include-untracked --patch stash@{0}
## => output
### diff --git a/README.md b/README.md
### index 1f2e3d4..5a6b7c8 100644
### --- a/README.md
### +++ b/README.md
### @@ -3,3 +3,5 @@
###  A practical reference for everyday Git commands.
###
###  See commands.md for useful Git commands.
### +
### +See tags.md for release tags.
### diff --git a/tags.md b/tags.md
### new file mode 100644
### index 0000000..e01bcc9
### --- /dev/null
### +++ b/tags.md
### @@ -0,0 +1 @@
### +# Release Tags

git status --short zeigt erfasste und nicht erfasste Pfade. Ein normales git diff zeigt nicht vorgemerkte Änderungen an erfassten Dateien, aber nicht den Inhalt einer neuen, noch nicht erfassten Datei wie tags.md. Mit --include-untracked wird diese Datei bei der Stash-Prüfung ebenfalls berücksichtigt.

Wie stellst du einen Stash wieder her oder löschst ihn?

bash
# apply a stash but keep it in the list
$ git stash apply stash@{1}
## => output
### On branch tutorial/stash
### Changes not staged for commit:
###         modified:   branches.md
$ git status --short
## => output
###  M branches.md

# apply the newest stash and remove it after success
$ git stash pop
## => output
### On branch tutorial/stash
### Changes not staged for commit:
###         modified:   README.md
###         modified:   branches.md
###
### Untracked files:
###         tags.md
###
### Dropped refs/stash@{0} (f0e1d2c3b4a5968778695a4b3c2d1e0f9a8b7c6d)
$ git status --short
## => output
###  M README.md
###  M branches.md
### ?? tags.md
$ git diff -- README.md branches.md

# delete one stash without applying it
$ git stash drop stash@{0}
## => output
### Dropped stash@{0} (1a2b3c4d5e6f7081928374655647382910abcdef)

# return this tutorial branch to a clean state
$ git restore README.md branches.md
$ rm tags.md
$ git status --short
## => output is empty because the working tree is clean
$ git switch main
# short version: git branch -D tutorial/stash
$ git branch --delete --force tutorial/stash

Das Anwenden eines Stashes kann Konflikte erzeugen. Löse sie wie Merge-Konflikte und merke die korrigierten Dateien anschließend vor.

Was bedeutet es, Änderungen in Git rückgängig zu machen?

Das kann heißen, eine Datei wiederherzustellen, sie aus dem Staging-Bereich zu entfernen, einen Commit umzukehren oder einen lokalen Branch auf einen früheren Commit zu verschieben. Der passende Befehl hängt davon ab, wo die Änderung gerade liegt.

Wie machst du Änderungen in Git rückgängig?

Beginne mit git status --short. Der zweistellige Status zeigt den Ort der Änderung: Die erste Spalte steht für den Staging-Bereich, die zweite für das Arbeitsverzeichnis. M README.md bedeutet geändert, aber nicht vorgemerkt. M README.md bedeutet vorgemerkt. ?? notes.txt kennzeichnet eine nicht erfasste Datei.

Git zeigt ein nicht vorgemerktes M in der zweiten Spalte meist rot und ein vorgemerktes M in der ersten Spalte grün an. git add README.md verschiebt das M von der roten zweiten Position ( M README.md) zur grünen ersten Position (M README.md). git restore --staged README.md verschiebt es zurück. Terminal-Themes und Git-Einstellungen können die Farben verändern. Die Spaltenposition ist deshalb der verlässliche Hinweis.

Wähle den Befehl passend zum Zustand:

  • git restore <file> verwirft nicht vorgemerkte Änderungen an einer erfassten Datei. Die Arbeitskopie wird dauerhaft durch die in Git gespeicherte Version ersetzt.
  • git restore --staged <file> entfernt eine Änderung aus dem Staging-Bereich, ohne den bearbeiteten Dateiinhalt zu verwerfen.
  • git commit --amend aktualisiert deinen letzten lokalen Commit, solange andere noch nicht darauf aufbauen. Dabei entsteht ein neuer Commit mit neuem Hash.
  • git revert <commit> macht eine committete Änderung rückgängig und erhält die bestehende Historie. Der Rückgängigmachungsschritt wird als neuer Commit gespeichert.
  • Eine nicht erfasste Datei ist noch nicht in Git gespeichert. git restore kann sie deshalb nicht wiederherstellen. Prüfe sie sorgfältig, bevor du sie mit git clean oder einem anderen Löschbefehl entfernst.

Wie stellst du eine Datei wieder her oder entfernst sie aus dem Staging-Bereich?

Ohne --staged verwirft git restore nicht vorgemerkte Änderungen an einer erfassten Datei. Mit --staged entfernt es nur die Vormerkung und behält den bearbeiteten Inhalt im Arbeitsverzeichnis.

bash
# create a branch and an unstaged change first
$ git switch --create tutorial/restore main
$ printf '\nTemporary introduction.\n' >> README.md
$ git status --short
## => output
###  M README.md
$ git diff -- README.md

# now discard the unstaged change in that tracked file
# WARNING: the discarded file changes cannot normally be recovered by Git
$ git restore README.md
$ git status --short
## => output is empty because the working tree is clean

# create and stage another change first
$ printf '\nAdd a quick-start section.\n' >> README.md
$ git add README.md
$ git status --short
## => output
### M  README.md
$ git diff --staged -- README.md

# now remove it from the staging area but keep it in the working tree
# short version: git restore -S README.md
$ git restore --staged README.md
$ git status --short
## => output
###  M README.md
$ git diff -- README.md

# discard the remaining working-directory change before the next example
$ git restore README.md
$ git status --short
## => output is empty because the working tree is clean

Das erste git restore README.md verwirft die vorübergehende Einleitung. Das spätere git restore --staged README.md behält dagegen den Schnellstart-Text: Das M wandert von der ersten in die zweite Spalte, normalerweise von Grün nach Rot. Die Änderung bleibt im Arbeitsverzeichnis, bis das abschließende git restore README.md sie verwirft.

Wie änderst du den letzten Commit nachträglich?

Amend ersetzt den letzten Commit durch eine korrigierte Version. Verwende es nur für deinen eigenen lokalen Commit, bevor andere auf ihm und seinem Hash aufbauen.

bash
# create an isolated branch and commit a change
$ git switch --create tutorial/amend main
$ printf '\nAdd a quick-start section.\n' >> README.md
$ git add README.md
$ git status --short
## => output
### M  README.md
$ git diff --staged -- README.md
# short version: git commit -m "Add quick-start note"
$ git commit --message "Add quick-start note"
$ printf '\nKeep the quick start concise.\n' >> README.md
$ git add README.md
$ git status --short
## => output
### M  README.md
$ git diff --staged -- README.md

# update the latest commit and reuse its existing message
$ git commit --amend --no-edit
## => output
### [tutorial/amend c3d4e5f] Add quick-start note
###  1 file changed, 4 insertions(+)

Der geänderte Commit behält die Nachricht Add quick-start note, enthält nun aber sowohl den Schnellstart-Abschnitt als auch dessen Korrektur. --no-edit bedeutet hier „den Editor für die Commit-Nachricht nicht öffnen“. Es bedeutet nicht „nichts ändern“: Git übernimmt die vorgemerkte Korrektur und ersetzt den ursprünglichen Commit durch einen neuen Commit mit neuem Hash.

Wie machst du einen Commit mit Revert rückgängig?

Revert erhält den vorhandenen Commit und erstellt einen neuen, der dessen Änderungen umkehrt. Dieses Beispiel committet zunächst eine neue Datei und macht den Commit danach rückgängig.

bash
# create an isolated branch for the revert example
$ git switch --create tutorial/revert main

# create and commit a new file
$ printf '# Restore Examples\n' > restore-examples.md
$ git add restore-examples.md
$ git status --short
## => output
### A  restore-examples.md
$ git diff --staged -- restore-examples.md
$ git commit --message "Add restore examples"
## => output
### [tutorial/revert d4e5f6a] Add restore examples
###  1 file changed, 1 insertion(+)
###  create mode 100644 restore-examples.md

Der neueste Commit heißt jetzt Add restore examples. Git bezeichnet den aktuell ausgecheckten Commit als HEAD. Der nächste Befehl kehrt die von HEAD eingeführten Änderungen um, ohne den Commit aus der Historie zu löschen.

bash
# reverse the latest commit and accept Git's generated message
$ git revert --no-edit HEAD
## => output
### [tutorial/revert e6f7a8b] Revert "Add restore examples"
###  1 file changed, 1 deletion(-)
###  delete mode 100644 restore-examples.md

# show that the original and reversing commits both remain in history
$ git log --oneline --decorate --max-count=2
## => output
### e6f7a8b (HEAD -> tutorial/revert) Revert "Add restore examples"
### d4e5f6a Add restore examples

# confirm that no uncommitted changes remain
$ git status --short
## => output is empty because the working tree is clean

Der erste Commit fügt restore-examples.md hinzu. Der zweite löscht die Datei und kehrt damit die Wirkung des ersten um. Danach zeigt HEAD auf den neuen Commit Revert "Add restore examples". Der ursprüngliche Commit bleibt direkt darunter im Log sichtbar. --no-edit überspringt den Nachrichten-Editor und übernimmt die von Git erzeugte Revert-Nachricht. Ein neuer Commit entsteht trotzdem.

git revert ist für geteilte Historie normalerweise die sicherste Wahl, weil bestehende Commits erhalten bleiben.

Wie setzt du lokale Commits zurück?

Reset verschiebt einen Branch auf einen anderen Commit. Der Modus bestimmt, was mit Staging-Bereich und Arbeitsverzeichnis passiert.

bash
# start from main and create a commit that changes a tracked file
$ git switch --create tutorial/reset main
$ printf '\nA reset example.\n' >> README.md
$ git add README.md
$ git status --short
## => output
### M  README.md
$ git diff --staged -- README.md
$ git commit --message "Add reset example"

# move HEAD back one commit and keep that change staged
$ git reset --soft HEAD~1
$ git status --short
## => output
### M  README.md

# recreate the commit before trying the next reset mode
$ git commit --message "Add reset example"

# move HEAD back one commit and keep the change unstaged
$ git reset HEAD~1
$ git status --short
## => output
###  M README.md
$ git diff -- README.md

# recreate the commit once more before demonstrating --hard
$ git add README.md
$ git status --short
## => output
### M  README.md
$ git diff --staged -- README.md
$ git commit --message "Add reset example"

# DANGER: move the branch and discard the committed tracked change
$ git reset --hard HEAD~1
## => output
### HEAD is now at 0f687a1 Merge branch retention guidance
$ git status --short
## => output is empty because the working tree is clean

Verwende reset nicht zum Umschreiben von Commits, die andere bereits nutzen.

Wie entfernst du nicht erfasste Dateien sicher?

git clean entfernt Dateien, die Git nicht erfasst. Führe es immer zuerst mit --dry-run aus, damit du genau prüfen kannst, was gelöscht würde.

bash
# create a disposable untracked file first
$ git switch --create tutorial/clean main
$ printf 'temporary notes\n' > notes.txt
$ git status --short
## => output
### ?? notes.txt

# preview untracked files without deleting anything
# short version: git clean -n
$ git clean --dry-run
## => output
### Would remove notes.txt

# only after checking the preview, delete the file
# short version: git clean -f
$ git clean --force
## => output
### Removing notes.txt
$ git status --short
## => output is empty because the working tree is clean

--dry-run zeigt eine Vorschau, --force erlaubt das tatsächliche Löschen. Die Kurzformen lauten -n und -f. Eine nicht erfasste Datei wurde nie committet; Git besitzt möglicherweise keine Kopie zur Wiederherstellung. Enthält die Vorschau etwas, das du behalten möchtest, verschiebe es, committe es oder sichere es mit git stash --include-untracked, bevor du aufräumst.

Wie pushst du umgeschriebene Historie möglichst sicher?

Rebase, Amend und Reset können Commit-Hashes verändern. Wenn du deinen eigenen Branch nach einem Push absichtlich umschreibst, lehnt Git einen normalen Push ab, weil die Historien nicht mehr zusammenpassen.

Wichtiger Hinweis: Pushe umgeschriebene Historie niemals ohne Abstimmung mit allen Betroffenen erzwungen auf einen geteilten oder geschützten Branch. Auch --force-with-lease verändert die Remote-Historie und kann Arbeit beeinträchtigen, die auf den ersetzten Commits aufbaut.

Die Push-Beispiele benötigen deinen eigenen beschreibbaren Fork. Ersetze <your-fork-url> durch dessen HTTPS- oder SSH-Klon-URL. Verwende die URL des ursprünglichen Repositories nur, wenn du es selbst verwaltest.

bash
# create a branch and publish it to a fork you can write to
$ git switch --create tutorial/force-push main
$ printf '\nForce-with-lease example.\n' >> README.md
$ git add README.md
$ git status --short
## => output
### M  README.md
$ git diff --staged -- README.md
$ git commit --message "Add force-with-lease example"
$ git remote add fork <your-fork-url>
# short version: git push -u fork tutorial/force-push
$ git push --set-upstream fork tutorial/force-push

# rewrite the branch locally
$ git commit --amend --message "Document force-with-lease"

# update your view of the fork before replacing its branch
$ git fetch fork

# replace the remote branch only if it still matches your known version
$ git push --force-with-lease fork tutorial/force-push

Bevorzuge --force-with-lease gegenüber --force. Ein Force-Push ersetzt den Remote-Branch auch dann, wenn jemand anderes Commits hinzugefügt hat. Die Lease ergänzt eine Prüfung: Der Push gelingt nur, wenn der Remote-Branch noch auf dem erwarteten Commit steht. Hat jemand Arbeit gepusht, die du noch nicht berücksichtigt hast, lehnt Git deinen Push ab, statt sie zu überschreiben. Hole und prüfe die neuen Remote-Commits, bevor du entscheidest, wie du fortfährst.

Was ist ein Rebase in Git?

Ein Rebase nimmt Commits aus einer Entwicklungslinie und spielt ihre Änderungen auf einer neuen Basis erneut ab. Die resultierenden Commits haben andere Vorgänger und neue Hashes.

Ein Merge verbindet Historien und kann einen Merge-Commit ergänzen. Ein Rebase erzeugt einen linearen Verlauf, als hätte die Arbeit auf der neueren Basis begonnen. Beide Ansätze können richtig sein. Für geteilte Historie ist Merge meist sicherer. Rebase eignet sich zum Aufräumen deines eigenen lokalen Feature-Branches, bevor du ihn teilst.

Stell dir vor, main und ein Feature-Branch haben sich seit Commit B weiterentwickelt:

text
main:       A---B---C
                 \
feature:          D---E

D und E gehören zum Feature-Branch, C ist der neueste Commit auf main.

Was macht ein Merge?

Ein Merge erhält beide Entwicklungslinien und verbindet sie. Mit einem Merge-Commit sieht das Ergebnis so aus:

text
main:       A---B---C-------M
                 \         /
feature:          D---E----/

Der Merge-Commit M hat C und E als Vorgänger. D und E behalten ihre Hashes, da Git sie nicht umgeschrieben hat. Auch der Punkt, an dem sich die Branches getrennt haben, bleibt im Graphen sichtbar.

Was macht ein Rebase?

Ein Rebase nimmt die Änderungen aus D und E und spielt sie nach C erneut ab:

text
Before rebase:

main:       A---B---C
                 \
feature:          D---E

After rebase:

main:       A---B---C---D'---E'

D' enthält die Änderungen aus D, E' die aus E. Es sind aber neue Commits mit anderen Vorgängern und Hashes. Der Verlauf ist linear und wirkt, als hätte die Feature-Arbeit erst nach C begonnen.

Wichtiger Hinweis: Schreibe grundsätzlich keine Commits um, die bereits auf einen Remote-Branch gepusht wurden. Andere könnten darauf aufbauen. Das Umschreiben kann Konflikte, doppelte Änderungen oder verlorene Arbeit verursachen. Ändere Remote-Historie nur nach ausdrücklicher Abstimmung im Team, wenn alle Betroffenen wissen, wie sie ihren Stand wieder in Ordnung bringen.

Beispielsweise kann ein Kollege noch die ursprünglichen Commits haben, während der Remote-Branch die durch Rebase erzeugten Ersatz-Commits enthält:

text
colleague:  A---B---D---E

remote:     A---B---C---D'---E'

Obwohl D und D' dieselbe Änderung einführen können, sieht Git sie als unterschiedliche Commits. Das Zusammenführen kann doppelte Änderungen und verwirrende Konflikte erzeugen. Als Faustregel gilt: Rebase eignet sich für deinen eigenen, noch nicht geteilten Feature-Branch. Sobald andere auf seinen Commits aufbauen, bevorzuge Merge.

Wie räumst du die Git-Historie mit Rebase auf?

Setze einen privaten Feature-Branch auf die neue Basis, löse mögliche Konflikte und überarbeite bei Bedarf aktuelle lokale Commits mit einem interaktiven Rebase.

Wie führst du einen Rebase eines Feature-Branches durch?

bash
# create two feature commits from main
$ git switch --create tutorial/rebase main
$ printf '\nPrefer git switch for branch changes.\n' >> commands.md
$ git add commands.md
$ git status --short
## => output
### M  commands.md
$ git diff --staged -- commands.md
$ git commit --message "Add switch tip"
$ printf '\nDelete merged branches when they are no longer needed.\n' >> branches.md
$ git add branches.md
$ git status --short
## => output
### M  branches.md
$ git diff --staged -- branches.md
$ git commit --message "Add branch cleanup tip"

# create a separate base branch from main and move it forward
$ git switch --create tutorial/rebase-base main
$ printf '\nAdvanced examples continue in the companion guide.\n' >> README.md
$ git add README.md
$ git status --short
## => output
### M  README.md
$ git diff --staged -- README.md
$ git commit --message "Link advanced examples"

# inspect both lines of history before rebasing
$ git switch tutorial/rebase
$ git log --oneline --graph --decorate --all --max-count=4
## => output
### * b7c8d9e (tutorial/rebase-base) Link advanced examples
### | * d4e5f6a (HEAD -> tutorial/rebase) Add branch cleanup tip
### | * a1b2c3d Add switch tip
### |/
### * 0f687a1 (origin/main, origin/HEAD, main) Merge branch retention guidance

# replay the feature commits on the newer local base
$ git rebase tutorial/rebase-base
## => output
### Successfully rebased and updated refs/heads/tutorial/rebase.

# inspect the rewritten branch history
$ git log --oneline --graph --decorate --all --max-count=4
## => output
### * f6a7b8c (HEAD -> tutorial/rebase) Add branch cleanup tip
### * c3d4e5f Add switch tip
### * b7c8d9e (tutorial/rebase-base) Link advanced examples
### * 0f687a1 (origin/main, origin/HEAD, main) Merge branch retention guidance

Vor dem Rebase zeigen die |-Zeichen, dass sich tutorial/rebase und tutorial/rebase-base von main aus auseinanderentwickelt haben. Danach bildet der Graph eine Linie: Git hat die beiden Feature-Commits hinter tutorial/rebase-base neu erstellt. Deshalb haben sie neue Hashes.

Wie löst du Rebase-Konflikte?

Bearbeite bei einem Konflikt die Datei, merke sie vor und fahre fort. Den Commit für die Konfliktlösung erstellst du während eines Rebases normalerweise nicht selbst.

bash
# create two branches that edit the same line differently
$ git switch --create tutorial/rebase-conflict main
$ printf '# Branching Guide\n\nKeep branches focused and short-lived.\n' > branches.md
$ git add branches.md
$ git status --short
## => output
### M  branches.md
$ git diff --staged -- branches.md
$ git commit --message "Recommend short-lived branches"

$ git switch --create tutorial/rebase-conflict-base main
$ printf '# Branching Guide\n\nDelete merged branches after review.\n' > branches.md
$ git add branches.md
$ git status --short
## => output
### M  branches.md
$ git diff --staged -- branches.md
$ git commit --message "Clarify branch cleanup"

# start the rebase to produce a real content conflict
$ git switch tutorial/rebase-conflict
$ git rebase tutorial/rebase-conflict-base
## => output
### CONFLICT (content): Merge conflict in branches.md
### error: could not apply 1a2b3c4... Recommend short-lived branches

# resolve the conflict, stage the corrected file and continue
$ printf '# Branching Guide\n\nKeep branches focused, short-lived and delete them after review.\n' > branches.md
$ git add branches.md
$ git status --short
## => output
### M  branches.md
$ git diff --staged -- branches.md
# continue the rebase; Git may open an editor for the commit message
$ git rebase --continue
## => output
### Successfully rebased and updated refs/heads/tutorial/rebase-conflict.

git rebase --continue kann deinen konfigurierten Editor öffnen, damit du die Commit-Nachricht bestätigst oder änderst. Wenn du sie behalten möchtest, lass den Text unverändert, speichere und schließe den Editor. Git setzt den Rebase erst fort, nachdem der Editor erfolgreich beendet wurde.

  • In Vim drückst du i, um die Nachricht zu bearbeiten. Danach drückst du Esc, tippst :wq und bestätigst mit Enter. Möchtest du nichts ändern, überspringe i und verwende direkt Esc, :wq und Enter.
  • In Nano bearbeitest du den Text direkt. Speichere mit Ctrl+O, bestätige den Dateinamen mit Enter und beende mit Ctrl+X.
  • In VS Code bearbeitest du den Text und speicherst mit Cmd+S unter macOS oder Ctrl+S unter Windows und Linux. Schließe anschließend den Tab oder das Fenster mit der Commit-Nachricht.

Beim gezeigten Konflikt würde git rebase --skip den angehaltenen Commit überspringen. git rebase --abort würde den gesamten Rebase abbrechen und tutorial/rebase-conflict in seinen ursprünglichen Zustand zurückversetzen. Wähle genau einen dieser Wege: --continue, --skip oder --abort.

Wie bearbeitest du Commits mit einem interaktiven Rebase?

Mit einem interaktiven Rebase kannst du die jüngere lokale Historie überarbeiten.

bash
# create three local commits to edit
$ git switch --create tutorial/interactive-rebase main
$ printf '# Stash Notes\n' > stash-notes.md
$ git add stash-notes.md
$ git status --short
## => output
### A  stash-notes.md
$ git diff --staged -- stash-notes.md
$ git commit --message "Add stash notes"
$ printf '\nUse descriptive stash messages.\n' >> stash-notes.md
$ git status --short
## => output
###  M stash-notes.md
$ git diff -- stash-notes.md
# short version: git commit -am "Add stash message tip"
$ git commit --all --message "Add stash message tip"
$ printf '\nInspect a stash before applying it.\n' >> stash-notes.md
$ git status --short
## => output
###  M stash-notes.md
$ git diff -- stash-notes.md
$ git commit --all --message "Add stash inspection tip"

# now edit those three unpublished commits
# short version: git rebase -i HEAD~3
$ git rebase --interactive HEAD~3

# in the editor:
# pick   = keep the commit
# reword = change its message
# squash = combine it with the previous commit
# drop   = remove it

# after saving and completing the rebase, inspect the rewritten history
$ git log --oneline --graph --decorate --max-count=4

Da Rebase die Commit-Hashes verändert, prüfe danach den Graphen und führe Tests aus.

Was ist Cherry-Pick in Git?

Cherry-Pick übernimmt die Änderung eines vorhandenen Commits und erstellt daraus einen neuen Commit auf deinem aktuellen Branch. Das ist hilfreich, wenn du eine kleine Fehlerbehebung kopieren möchtest, ohne den gesamten Quell-Branch zu mergen.

Die Änderung erscheint dadurch jedoch doppelt in der Historie. Verwende Cherry-Pick nicht für lange Commit-Reihen, wenn Merge oder Rebase den Zusammenhang besser ausdrücken würden.

Wie übernimmst du einen Commit mit Cherry-Pick?

<cherry-pick-hash> ist ein Platzhalter. Führe den gezeigten git log-Befehl aus, kopiere den Hash aus der ersten Spalte und ersetze den Platzhalter damit.

bash
# create a source branch and the fix that will be copied
$ git switch --create tutorial/branch-tip main
$ printf '\nUse git branch --merged before deleting branches.\n' >> branches.md
$ git add branches.md
$ git status --short
## => output
### M  branches.md
$ git diff --staged -- branches.md
$ git commit --message "Add branch cleanup tip"

# show the newest commit on the source branch and copy its hash
$ git log --max-count=1 --oneline --decorate tutorial/branch-tip
## => output
### 7a8b9c0 (HEAD -> tutorial/branch-tip) Add branch cleanup tip

# create the target branch from main
$ git switch --create tutorial/cherry-pick main
## => output
### Switched to a new branch 'tutorial/cherry-pick'
$ printf '\nCherry-pick selected fixes into this branch.\n' >> README.md
$ git add README.md
$ git status --short
## => output
### M  README.md
$ git diff --staged -- README.md
$ git commit --message "Prepare cherry-pick target"

# replace the placeholder with the hash copied from git log
$ git cherry-pick <cherry-pick-hash>
## => output
### [tutorial/cherry-pick 0d1e2f3] Add branch cleanup tip
###  1 file changed, 2 insertions(+), 1 deletion(-)

Was sind Git-Tags?

Ein Branch zeigt auf den neuesten Commit einer laufenden Entwicklungslinie und wandert bei neuen Commits weiter. Ein Tag ist dagegen eine dauerhafte Bezeichnung für einen bestimmten Commit, häufig für Releases wie v1.0.0.

Ein Lightweight-Tag besteht nur aus einem Namen. Ein annotierter Tag speichert zusätzlich Nachricht, Ersteller und Datum und kann signiert werden. Für Releases sind annotierte Tags die bessere Voreinstellung.

Wie erstellst und teilst du Git-Tags?

bash
# create a release branch and a release commit first
$ git switch --create tutorial/release main
$ printf '\nRelease: 1.0.0\n' >> README.md
$ git add README.md
$ git status --short
## => output
### M  README.md
$ git diff --staged -- README.md
$ git commit --message "Prepare version 1.0.0"

# create an annotated release tag at HEAD
# short version: git tag -a v1.0.0 -m "Release version 1.0.0"
$ git tag --annotate v1.0.0 --message "Release version 1.0.0"

# list and inspect tags
# short version: git tag -l
$ git tag --list
## => output
### v1.0.0
$ git show v1.0.0
## => output
### tag v1.0.0
### Tagger: Ada Lovelace <ada@example.com>
### Date:   Thu Aug 6 09:00:00 2026 +0200
###
### Release version 1.0.0
###
### commit b7c8d9e (HEAD -> tutorial/release, tag: v1.0.0)
### Author: Ada Lovelace <ada@example.com>
### Date:   Wed Aug 5 11:00:00 2026 +0200
###
###     Prepare version 1.0.0

# add your writable fork and push one tag; a normal push does not push every local tag
$ git remote add release-fork <your-fork-url>
$ git push release-fork v1.0.0

# delete a local tag
# short version: git tag -d v1.0.0
$ git tag --delete v1.0.0
## => output
### Deleted tag 'v1.0.0' (was 4f5a6b7)

# delete the remote tag
# short version: git push -d release-fork v1.0.0
$ git push release-fork --delete v1.0.0

Verschiebe keine bereits veröffentlichten Release-Tags. Erstelle eine neue Version, wenn sich der veröffentlichte Inhalt ändert.

Was ist Git Bisect?

git bisect findet mit binärer Suche den Commit, der einen Fehler eingeführt hat. Du benennst einen fehlerhaften und einen älteren, funktionierenden Commit. Git checkt einen Commit dazwischen aus. Nach jeder Rückmeldung „gut“ oder „schlecht“ fällt die Hälfte der verbliebenen Kandidaten weg.

Wie findest du einen Fehler mit Git Bisect?

Du kannst jeden ausgewählten Commit selbst testen oder Git automatisch einen Testbefehl ausführen lassen, bis der erste fehlerhafte Commit gefunden ist.

Wie testest du Commits manuell?

Dieses Beispiel erstellt den Lightweight-Tag bisect-good am bekannten funktionierenden Commit. Damit hat dieser Endpunkt einen einprägsamen Namen, und du musst seinen Hash nicht in jeden Befehl kopieren. Das ist nur eine Erleichterung: git bisect good akzeptiert auch den vollständigen oder einen eindeutigen gekürzten Commit-Hash, etwa git bisect good a1b2c3d. Ein vorhandener Release-Tag funktioniert ebenfalls, wenn dieses Release den Fehler noch nicht enthielt.

bash
# create a history with a known-good commit and a later regression
$ git switch --create tutorial/bisect main
$ printf 'safe\n' > mode.txt
$ git add mode.txt
$ git status --short
## => output
### A  mode.txt
$ git diff --staged -- mode.txt
$ git commit --message "Add safe mode"
$ git tag bisect-good
$ printf 'safe\ninclude examples\n' > mode.txt
$ git status --short
## => output
###  M mode.txt
$ git diff -- mode.txt
$ git commit --all --message "Add mode examples"
$ printf 'broken\ninclude examples\n' > mode.txt
$ git status --short
## => output
###  M mode.txt
$ git diff -- mode.txt
$ git commit --all --message "Introduce mode regression"
$ printf '\nbisect these changes\n' >> README.md
$ git add README.md
$ git status --short
## => output
### M  README.md
$ git diff --staged -- README.md
$ git commit --message "Document mode behavior"

# start the search and mark the current commit as broken
$ git bisect start
## => output
### status: waiting for both good and bad commits
$ git bisect bad
## => output
### status: waiting for good commit(s), bad commit known

# mark the known working tag
$ git bisect good bisect-good
## => output
### Bisecting: 0 revisions left to test after this (roughly 1 step)
### [3c4d5e6] Introduce mode regression

# inspect the checked-out version, then answer good or bad
$ cat mode.txt
## => output
### broken
### include examples
$ git bisect bad
## => output
### Bisecting: 0 revisions left to test after this (roughly 0 steps)
### [5d6e7f8] Add mode examples

# this earlier commit still has safe mode, so mark it good
$ cat mode.txt
## => output
### safe
### include examples
$ git bisect good
## => output
### 3c4d5e6 is the first bad commit
### commit 3c4d5e6
### Author: Ada Lovelace <ada@example.com>
### Date:   Fri Aug 7 14:00:00 2026 +0200
###
###     Introduce mode regression

# after Git identifies the first bad commit, leave bisect mode
$ git bisect reset
## => output
### Previous HEAD position was 5d6e7f8 Add mode examples
### Switched to branch 'tutorial/bisect'

Wie automatisierst du Bisect mit einem Test?

Ein Test für eine Regression existiert oft noch nicht in älteren Commits. Schreibe dann ein kleines Reproduktionsskript und behalte es während der Suche unverändert bei. Es muss nicht von Git erfasst sein. Es kann als nicht erfasste Datei im Repository oder außerhalb liegen, etwa unter /tmp/bisect-regression.sh. Außerhalb vermeidest du Konflikte mit historischen Dateien am selben Pfad.

Führe das Skript vor Beginn an beiden Endpunkten aus. Beim funktionierenden Commit muss es mit 0 enden, beim fehlerhaften mit einem Wert von 1 bis 127, ausgenommen 125. Exit-Code 125 bedeutet, dass dieser Commit nicht getestet werden kann und übersprungen werden soll.

Der folgende printf-Befehl schreibt ein zweizeiliges Skript. Das Format %s\n gibt jede folgende Zeichenkette in einer eigenen Zeile aus. > erstellt oder überschreibt /tmp/bisect-regression.sh. Die erste Zeile, #!/bin/sh, legt die System-Shell als Interpreter fest. Die zweite sucht in mode.txt nach einer Zeile, die genau broken enthält. -q unterdrückt die normale Ausgabe, ^ und $ begrenzen den Treffer auf die ganze Zeile. Das vorangestellte ! kehrt das Ergebnis um: Wird broken gefunden, endet der Test mit 1, andernfalls mit 0. Das funktioniert hier, weil mode.txt in allen Commits zwischen den Endpunkten existiert.

bash
# create a fixed test that succeeds for good commits and fails for bad ones
$ printf '%s\n' '#!/bin/sh' '! grep -q "^broken$" mode.txt' > /tmp/bisect-regression.sh
$ chmod +x /tmp/bisect-regression.sh

# verify that it succeeds at the good endpoint
# short version: git switch -d bisect-good
$ git switch --detach bisect-good
$ /tmp/bisect-regression.sh
## => output
### no output; exit status 0 means good

# verify that it fails at the bad endpoint
$ git switch tutorial/bisect
$ /tmp/bisect-regression.sh
## => output
### no output; exit status 1 means bad

$ git bisect start HEAD bisect-good
## => output
### Bisecting: 0 revisions left to test after this (roughly 1 step)
### [3c4d5e6] Introduce mode regression
$ git bisect run /tmp/bisect-regression.sh
## => output
### running '/tmp/bisect-regression.sh'
### running '/tmp/bisect-regression.sh'
### 3c4d5e6 is the first bad commit
### bisect found first bad commit
$ git bisect reset
## => output
### Previous HEAD position was 5d6e7f8 Add mode examples
### Switched to branch 'tutorial/bisect'

# remove the temporary test script after the search
$ rm /tmp/bisect-regression.sh

Ein sauberes Arbeitsverzeichnis ist dringend zu empfehlen, da Git viele Commits auschecken muss. Änderungen an erfassten Dateien können das verhindern; nicht erfasste Build-Artefakte können das Ergebnis beeinflussen.

Was ist Git Reflog?

Das Reflog zeichnet auf, wohin lokale Referenzen wie HEAD gezeigt haben. Es hilft nach einem versehentlichen Reset, Rebase oder dem Löschen eines Branches, auch wenn der Commit nicht mehr in git log auftaucht.

Wie stellst du verlorene Arbeit mit Git Reflog wieder her?

bash
# create a commit that will deliberately become unreachable from its branch
$ git switch --create tutorial/reflog main
$ printf '# Recovery Notes\n' > recovery-notes.md
$ git add recovery-notes.md
$ git status --short
## => output
### A  recovery-notes.md
$ git diff --staged -- recovery-notes.md
$ git commit --message "Add recovery notes"
## => output
### [tutorial/reflog e6f7a8b] Add recovery notes
$ lost_commit=$(git rev-parse HEAD)

# remove that commit from the branch before looking for it in the reflog
$ git reset --hard HEAD~1

# inspect recent movements of HEAD
$ git reflog
## => output
### 0f687a1 (HEAD -> tutorial/reflog, main) HEAD@{0}: reset: moving to HEAD~1
### e6f7a8b HEAD@{1}: commit: Add recovery notes
### 0f687a1 HEAD@{2}: checkout: moving from tutorial/bisect to tutorial/reflog

# inspect a candidate before restoring it
$ git show "$lost_commit"
## => output
### commit e6f7a8b
### Author: Ada Lovelace <ada@example.com>
### Date:   Sat Aug 8 09:30:00 2026 +0200
###
###     Add recovery notes

# create a recovery branch without changing current work
$ git branch recovery/lost-work "$lost_commit"

Reflogs sind lokal und ihre Einträge laufen ab. Sie sind ein Sicherheitsnetz, keine Backup-Strategie.

Was ist Git Worktree?

Ein Worktree ermöglicht mehrere Arbeitsverzeichnisse für ein Repository. So kannst du dein Feature in einem Verzeichnis offen lassen und in einem anderen eine dringende Fehlerbehebung prüfen, ohne ständig zu stashen und Branches zu wechseln.

Wie arbeitest du mit Git Worktree auf mehreren Branches?

bash
# keep the primary working directory on main
$ git switch main

# create a new branch based on main
$ git branch hotfix/branch-wording main

# check out the new branch in another working directory
$ git worktree add ../the-ultimate-git-cheat-sheet-hotfix hotfix/branch-wording
## => output
### Preparing worktree (checking out 'hotfix/branch-wording')
### HEAD is now at 0f687a1 Merge branch retention guidance

# list every worktree
$ git worktree list
## => output
### /Users/ada/the-ultimate-git-cheat-sheet         0f687a1 [main]
### /Users/ada/the-ultimate-git-cheat-sheet-hotfix  0f687a1 [hotfix/branch-wording]

# work in the second directory
$ cd ../the-ultimate-git-cheat-sheet-hotfix
$ printf '\nKeep branch names short and descriptive.\n' >> branches.md
$ git add branches.md
$ git status --short
## => output
### M  branches.md
$ git diff --staged -- branches.md
$ git commit --message "Clarify branch naming"

# after committing the change, return and remove the extra working directory
$ cd ../the-ultimate-git-cheat-sheet
$ git worktree remove ../the-ultimate-git-cheat-sheet-hotfix

Derselbe Branch kann normalerweise nicht gleichzeitig in zwei Worktrees ausgecheckt sein. Sichere wichtige Dateien, bevor du einen Worktree mit nicht committeten Änderungen entfernst.

Was sind Git-Submodule?

Mit einem Submodul verweist ein Repository auf einen bestimmten Commit eines anderen Repositories. Das übergeordnete Repository speichert diesen Verweis, nicht die vollständige Historie des eingebundenen Repositories in seiner eigenen Historie.

Submodule können für unabhängig versionierte Abhängigkeiten sinnvoll sein, erhöhen aber die Komplexität. Ein Submodul-Verzeichnis kann vorhanden wirken, obwohl sein Inhalt noch nicht initialisiert wurde. Außerdem aktualisiert eine Änderung im eingebundenen Repository nicht automatisch den Verweis im übergeordneten Repository.

Wie arbeitest du mit Git-Submodulen?

Füge ein Submodul hinzu oder aktualisiere es, wenn das übergeordnete Repository auf ein anderes Repository verweisen soll. Beachte beim Entfernen den vollständigen Aufräumprozess.

Wie fügst du ein Submodul hinzu und aktualisierst es?

bash
# create an isolated branch for the submodule example
$ git switch --create tutorial/submodule main

# add the Ultimate Git Cheat Sheet itself as a nested reference for this demonstration
$ git submodule add https://github.com/aichbauer/the-ultimate-git-cheat-sheet.git vendor/git-reference
$ git status --short
## => output
### A  .gitmodules
### A  vendor/git-reference
$ git diff --staged -- .gitmodules vendor/git-reference
$ git commit --message "Add Git reference submodule"
## => output
### [tutorial/submodule 4a5b6c7] Add Git reference submodule
###  2 files changed, 4 insertions(+)
###  create mode 100644 .gitmodules
###  create mode 160000 vendor/git-reference

# clone a project and initialize all submodules in one step
$ cd ..
# short version: git clone --recurse-submodules -b tutorial/submodule the-ultimate-git-cheat-sheet ultimate-git-with-submodule
$ git clone --recurse-submodules --branch tutorial/submodule the-ultimate-git-cheat-sheet ultimate-git-with-submodule

# make a normal clone as well, then initialize its submodule separately
$ git clone --branch tutorial/submodule the-ultimate-git-cheat-sheet ultimate-git-normal-clone
$ cd ultimate-git-normal-clone
$ git submodule update --init --recursive

# fetch and check out the configured remote branch for each submodule
$ git submodule update --remote --recursive

Wie entfernst du ein Submodul?

Deinitialisiere es zuerst, entferne dann seinen erfassten Pfad und committe die Änderung.

bash
# short version: git submodule deinit -f vendor/git-reference
$ git submodule deinit --force vendor/git-reference
## => output
### Cleared directory 'vendor/git-reference'
### Submodule 'vendor/git-reference' (https://github.com/aichbauer/the-ultimate-git-cheat-sheet.git) unregistered for path 'vendor/git-reference'
# short version: git rm -f vendor/git-reference
$ git rm --force vendor/git-reference
## => output
### rm 'vendor/git-reference'
$ git status --short
## => output
### M  .gitmodules
### D  vendor/git-reference
$ git diff --staged -- .gitmodules vendor/git-reference
$ git commit --message "Remove Git reference submodule"
## => output
### [tutorial/submodule 8d9e0f1] Remove Git reference submodule
###  2 files changed, 4 deletions(-)
###  delete mode 160000 vendor/git-reference

Was hast du gelernt?

Du kannst jetzt unfertige Änderungen zwischenspeichern, deine noch nicht geteilte Historie überarbeiten, einzelne Commits übernehmen, Releases markieren und den Commit finden, der einen Fehler eingeführt hat. Außerdem weißt du, wie Reflog, Worktrees und Submodule weniger alltägliche Git-Probleme lösen.

Setze diese Werkzeuge bewusst ein. Prüfe vor dem Umschreiben der Historie den Repository-Zustand, vermeide Rebase bei Commits, die andere bereits nutzen, und erstelle vor einem destruktiven Eingriff einen Branch zur Wiederherstellung.

Für den täglichen Git-Ablauf und die grundlegenden Befehle kehre zum ultimativen Git Cheat Sheet zurück.

Komm in unsere Community

Hat dir dieser Artikel gefallen? Teile ihn mit deinen Kolleginnen, Kollegen und Freunden.

Melde dich für unseren Newsletter an!

Verpasse keine neuen Tipps, Anleitungen und Updates – melde dich jetzt für unseren Newsletter an! Wir schicken dir nur relevante und hilfreiche Informationen.

Mit deiner Anmeldung akzeptierst du die Datenschutzerklärung. Du kannst dich jederzeit über den Link am Ende unserer E-Mails abmelden.

Das Git Cheat Sheet für Fortgeschrittene