Zum Inhalt springen
·22 Min. Lesezeit

Git jenseits der Grundlagen: Workflows, die jede Woche Stunden sparen

Interactive Rebase, Cherry-Pick, Bisect, Worktrees, Reflog-Rettung und die Branching-Strategien, die wirklich funktionieren. Git-Befehle, die ich täglich verwende und die die meisten Entwickler nicht kennen.

Teilen:X / TwitterLinkedIn

Die meisten Entwickler lernen fünf Git-Befehle und hören auf. add, commit, push, pull, merge. Vielleicht checkout und branch, wenn sie abenteuerlustig sind. Damit kommt man durch das erste Jahr. Dann hat dein Branch 47 Commits mit Nachrichten wie „fix" und „wip" und „bitte funktioniere", du hast versehentlich etwas zurückgesetzt, das du nicht hättest zurücksetzen sollen, und du verbringst 40 Minuten auf Stack Overflow damit, einen misslungenen Merge rückgängig zu machen.

Ich benutze Git seit Jahren. Nicht beiläufig — intensiv. Mehrere Branches, mehrere Repos, mehrere Mitarbeiter, den ganzen Tag, jeden Tag. Was folgt, sind die Befehle und Workflows, die ich tatsächlich verwende. Nicht die, die in einem Tutorial gut aussehen. Die, die mir jede einzelne Woche echte Zeit sparen.

Interactive Rebase: Räum deinen Mist auf, bevor ihn jemand sieht#

Dein Branch hat zwölf Commits. Die Hälfte davon ist „fix typo". Einer sagt „vorherigen Commit rückgängig machen". Ein anderer sagt „diesmal wirklich fixen". Du bist dabei, einen PR zu öffnen. Niemand muss diese History sehen.

Interactive Rebase ist, wie du die History auf deinem Branch umschreibst, bevor du ihn teilst. Damit kannst du Commits zusammenquetschen, Nachrichten umformulieren, sie umordnen oder komplett verwerfen.

Der grundlegende Befehl#

bash
git rebase -i HEAD~5

Das öffnet einen Editor, der deine letzten 5 Commits zeigt, älteste zuerst:

bash
pick a1b2c3d Add user authentication endpoint
pick d4e5f6g Fix typo in auth middleware
pick h7i8j9k Add rate limiting
pick l0m1n2o Fix rate limit bug
pick p3q4r5s Update auth tests

Jede Zeile beginnt mit einem Befehl. Ändere pick zu einem dieser:

  • squash (oder s) — Diesen Commit mit dem darüber zusammenführen, Nachrichten kombinieren
  • fixup (oder f) — Wie Squash, aber die Nachricht dieses Commits verwerfen
  • reword (oder r) — Commit behalten aber seine Nachricht ändern
  • drop (oder d) — Diesen Commit komplett löschen
  • edit (oder e) — Den Rebase bei diesem Commit pausieren, damit du ihn bearbeiten kannst

Eine echte Aufräum-Session#

Hier ist, was ich tatsächlich mache. Die unordentliche History von oben wird zu:

bash
pick a1b2c3d Add user authentication endpoint
fixup d4e5f6g Fix typo in auth middleware
pick h7i8j9k Add rate limiting
fixup l0m1n2o Fix rate limit bug
pick p3q4r5s Update auth tests

Speichern und schließen. Jetzt hast du drei saubere Commits statt fünf. Der Typo-Fix wird in den Auth-Commit gefaltet. Der Rate-Limit-Bug-Fix wird in den Rate-Limit-Commit gefaltet. Dein PR-Reviewer sieht einen sauberen, logischen Fortschritt.

Commits umordnen#

Du kannst die Zeilen buchstäblich umordnen. Wenn der Test-Commit vor dem Rate-Limiting-Commit kommen soll, verschiebe einfach die Zeile:

bash
pick a1b2c3d Add user authentication endpoint
pick p3q4r5s Update auth tests
pick h7i8j9k Add rate limiting

Git spielt deine Commits in dieser neuen Reihenfolge ab. Wenn es Konflikte gibt, pausiert es und lässt dich sie lösen.

Die Autosquash-Abkürzung#

Wenn du weißt, dass ein Commit ein Fix für einen früheren ist, markiere ihn beim Commit-Zeitpunkt:

bash
git commit --fixup=a1b2c3d

Das erstellt einen Commit mit der Nachricht fixup! Add user authentication endpoint. Dann beim Rebase:

bash
git rebase -i --autosquash HEAD~5

Git ordnet die Fixup-Commits automatisch direkt unter ihren Zielen ein und markiert sie als fixup. Du musst nur speichern und schließen. Kein manuelles Bearbeiten.

Ich benutze das ständig. Es ist der schnellste Weg, auf einem Branch zu iterieren und dabei die finale History sauber zu halten.

Die goldene Regel#

Rebase niemals Commits, die auf einen geteilten Branch gepusht wurden. Wenn andere Leute Arbeit auf diesen Commits aufgebaut haben, wird das Umschreiben der History echte Probleme verursachen. Rebase deine eigenen Feature-Branches vor dem Mergen. Rebase niemals main.

Wenn du deinen Feature-Branch schon gepusht hast und rebasing brauchst:

bash
git push --force-with-lease

Das --force-with-lease-Flag ist sicherer als --force. Es weigert sich zu pushen, wenn jemand anderes seit deinem letzten Fetch auf denselben Branch gepusht hat. Es verhindert nicht alle Probleme, aber es fängt das häufigste ab.

Cherry-Pick: Chirurgische Commit-Transfers#

Cherry-Pick nimmt einen bestimmten Commit von einem Branch und wendet ihn auf einen anderen an. Kein Merge. Kein Rebase. Nur ein Commit, sauber angewendet.

Wann ich das tatsächlich verwende#

Das häufigste Szenario: Ich habe einen Bug auf meinem Feature-Branch gefixt, aber der Fix muss auch sofort auf main oder einen Release-Branch. Ich will nicht meinen gesamten Feature-Branch mergen. Ich will nur diesen einen Fix.

bash
# Den Commit-Hash des Fixes finden
git log --oneline feature/user-auth
# a1b2c3d Fix null pointer in session validation
 
# Zu main wechseln und cherry-picken
git checkout main
git cherry-pick a1b2c3d

Fertig. Der Fix ist auf main als neuer Commit mit denselben Änderungen.

Cherry-Pick ohne Committen#

Manchmal willst du die Änderungen anwenden, aber noch nicht committen. Vielleicht willst du mehrere Cherry-Picks zu einem Commit kombinieren oder die Änderungen leicht modifizieren:

bash
git cherry-pick --no-commit a1b2c3d

Die Änderungen sind gestaged aber nicht committed. Du kannst sie modifizieren, weitere Änderungen hinzufügen und dann committen, wenn du bereit bist.

Bereichs-Cherry-Pick#

Brauchst du mehrere aufeinanderfolgende Commits? Verwende Bereichssyntax:

bash
git cherry-pick a1b2c3d..f6g7h8i

Das cherry-pickt alles nach a1b2c3d bis einschließlich f6g7h8i. Beachte, dass a1b2c3d selbst ausgeschlossen ist. Wenn du ihn einschließen willst:

bash
git cherry-pick a1b2c3d^..f6g7h8i

Konflikte behandeln#

Cherry-Pick-Konflikte funktionieren wie Merge-Konflikte. Wenn einer auftritt:

bash
# Git sagt dir, dass es einen Konflikt gibt
# Behebe die konfliktierenden Dateien, dann:
git add .
git cherry-pick --continue

Oder wenn du es dir anders überlegst:

bash
git cherry-pick --abort

Eine Sache, auf die du achten solltest: Cherry-gepickte Commits erstellen neue Commit-Hashes. Wenn du später den Original-Branch mergst, ist Git normalerweise schlau genug, die Duplikation zu handhaben. Aber wenn du aggressiv zwischen Branches cherry-pickst, die irgendwann zusammengeführt werden, könntest du unerwartete Konflikte sehen. Verwende es chirurgisch, nicht als Merge-Strategie.

Git Bisect: Binäre Suche nach Bugs#

Etwas ist kaputt. Du weißt, es funktionierte vor zwei Wochen. Es gab 200 Commits seitdem. Welcher hat es kaputt gemacht?

Du könntest jeden Commit manuell prüfen. Oder du könntest git bisect verwenden, das binäre Suche nutzt, um den exakten brechenden Commit in log2(n) Schritten zu finden. Bei 200 Commits sind das etwa 7-8 Prüfungen statt 200.

Der manuelle Weg#

bash
# Bisecting starten
git bisect start
 
# Aktuellen Commit als schlecht markieren (der Bug existiert hier)
git bisect bad
 
# Einen bekannten guten Commit markieren (der Bug existierte hier nicht)
git bisect good v2.1.0

Git checkt einen Commit auf halbem Weg zwischen gut und schlecht aus. Teste ihn. Dann:

bash
# Wenn der Bug bei diesem Commit existiert:
git bisect bad
 
# Wenn der Bug bei diesem Commit nicht existiert:
git bisect good

Git halbiert den Bereich jedes Mal. Nach 7-8 Schritten sagt es dir:

bash
a1b2c3d4e5f6g7h is the first bad commit
commit a1b2c3d4e5f6g7h
Author: Some Developer <dev@example.com>
Date: Tue Feb 18 14:23:01 2026 +0300
 
    Refactor session handling to use async middleware

Jetzt weißt du genau, welcher Commit den Bug eingeführt hat. Wenn du fertig bist:

bash
git bisect reset

Das bringt dich zurück dahin, wo du angefangen hast.

Der automatisierte Weg (Das ist die wahre Stärke)#

Wenn du einen Test hast, der den Bug erkennen kann, kannst du das Ganze automatisieren:

bash
git bisect start
git bisect bad HEAD
git bisect good v2.1.0
git bisect run npm test -- --grep "session validation"

Git wird automatisch Commits auschecken, den Test ausführen und sie basierend auf dem Exit-Code als gut oder schlecht markieren. Null bedeutet gut, nicht-null bedeutet schlecht. Geh weg, komm zurück, und es sagt dir den exakten Commit.

Du kannst jedes Script verwenden:

bash
git bisect run ./test-regression.sh

Wobei test-regression.sh ist:

bash
#!/bin/bash
npm run build 2>/dev/null || exit 125  # 125 bedeutet "diesen Commit überspringen"
npm test -- --grep "session" || exit 1  # 1 bedeutet "schlecht"
exit 0                                   # 0 bedeutet "gut"

Exit-Code 125 ist besonders — er sagt Bisect, diesen Commit zu überspringen (nützlich, wenn ein Commit nicht kompiliert). Das ist eines dieser Features, das nischenhaft erscheint, bis du es brauchst, und dann rettet es dir einen ganzen Nachmittag.

Eine echte Bisect-Session#

So sieht es in der Praxis aus:

bash
$ git bisect start
$ git bisect bad HEAD
$ git bisect good abc1234
Bisecting: 97 revisions left to test after this (roughly 7 steps)
[def5678...] Commit message here
 
$ npm test -- --grep "login flow"
# Tests schlagen fehl
 
$ git bisect bad
Bisecting: 48 revisions left to test after this (roughly 6 steps)
[ghi9012...] Another commit message
 
$ npm test -- --grep "login flow"
# Tests bestanden
 
$ git bisect good
Bisecting: 24 revisions left to test after this (roughly 5 steps)
...
 
# Nach ~7 Iterationen:
abc1234def5678ghi is the first bad commit

Sieben Schritte, um eine Nadel im Heuhaufen von 200 Commits zu finden.

Git Worktrees: Mehrere Branches, null Stashing#

Das ist das am meisten unterbewertete Git-Feature. Ich bin überzeugt, dass die meisten Entwickler nicht wissen, dass es existiert.

Das Problem: Du steckst tief in einem Feature-Branch. Dateien sind überall geändert. Dann sagt jemand „Kannst du dir kurz diesen Produktions-Bug anschauen?" Du hast drei Optionen:

  1. Alles stashen, Branches wechseln, fixen, zurückwechseln, Stash poppen. Hoffen, dass nichts schiefgeht.
  2. Deine halbfertige Arbeit mit einer „wip"-Nachricht committen. Hässlich aber funktional.
  3. Das Repo nochmal in ein anderes Verzeichnis klonen.

Oder Option 4: Worktrees.

Was ein Worktree ist#

Ein Worktree ist ein zweites (oder drittes, oder viertes) Arbeitsverzeichnis, das mit demselben Repository verknüpft ist. Jeder Worktree hat seinen eigenen ausgecheckten Branch, seine eigenen Arbeitsdateien, seinen eigenen Index. Aber sie teilen dieselben .git-Daten, du duplizierst also nicht das gesamte Repo.

Einen Worktree hinzufügen#

bash
# Du bist auf feature/user-auth, tief in der Arbeit
# Musst einen Bug auf main fixen:
 
git worktree add ../hotfix-session main

Das erstellt ein neues Verzeichnis ../hotfix-session mit ausgechecktem main. Dein aktuelles Verzeichnis bleibt exakt so wie es war. Nichts gestasht, nichts committed, nichts gestört.

bash
cd ../hotfix-session
# Den Bug fixen
git add .
git commit -m "Fix null pointer in session validation"
git push origin main
cd ../my-project
# Weiter an deinem Feature arbeiten, als wäre nichts passiert

Einen neuen Branch in einem Worktree erstellen#

bash
git worktree add ../hotfix-nav -b hotfix/nav-crash main

Das erstellt den Worktree UND erstellt einen neuen Branch hotfix/nav-crash basierend auf main.

Worktrees verwalten#

bash
# Alle Worktrees auflisten
git worktree list
# /home/dev/my-project         abc1234 [feature/user-auth]
# /home/dev/hotfix-session     def5678 [main]
 
# Einen Worktree entfernen wenn fertig
git worktree remove ../hotfix-session
 
# Wenn das Verzeichnis schon gelöscht wurde:
git worktree prune

Warum das besser ist als Stashing#

Stashing ist gut für schnelle Kontextwechsel. Aber Worktrees sind besser für alles, das länger als fünf Minuten dauert:

  • Deine IDE bleibt auf deinem Feature-Branch offen. Kein Neuindizieren, kein Verlust deiner Scrollposition.
  • Du kannst Tests in beiden Verzeichnissen gleichzeitig ausführen.
  • Kein Risiko von Stash-Konflikten oder Vergessen, was du gestasht hast.
  • Du kannst einen lang laufenden Dev-Server in einem Worktree haben und einen sauberen Build in einem anderen.

Ich halte typischerweise zwei oder drei Worktrees aktiv: meinen Haupt-Feature-Branch, einen main-Worktree für schnelle Checks, und manchmal einen Review-Worktree, wo ich den PR von jemand anderem auschecke.

Der eine Haken#

Du kannst nicht denselben Branch in zwei Worktrees ausgecheckt haben. Das ist beabsichtigt — es verhindert, dass du widersprüchliche Änderungen am selben Branch an zwei Stellen machst. Wenn du es versuchst, wird Git es verweigern.

Reflog: Der Rückgängig-Button für alles#

Du hast einen Hard Reset gemacht und Commits verloren. Du hast einen Branch gelöscht. Du hast rebased und alles ging fürchterlich schief. Du denkst, deine Arbeit ist weg.

Ist sie nicht. Git löscht fast nie tatsächlich etwas. Das Reflog ist dein Sicherheitsnetz.

Was das Reflog ist#

Jedes Mal, wenn HEAD sich bewegt — jeder Commit, Checkout, Rebase, Reset, Merge — zeichnet Git es im Reflog auf. Es ist ein Protokoll davon, wo dein HEAD war, der Reihe nach.

bash
git reflog

Ausgabe:

bash
a1b2c3d (HEAD -> main) HEAD@{0}: reset: moving to HEAD~3
f4e5d6c HEAD@{1}: commit: Add payment processing
b7a8c9d HEAD@{2}: commit: Update user dashboard
e0f1g2h HEAD@{3}: commit: Fix auth token refresh
i3j4k5l HEAD@{4}: checkout: moving from feature/payments to main

Jeder Eintrag hat einen Index (HEAD@{0}, HEAD@{1}, etc.) und eine Beschreibung, was passiert ist.

Wiederherstellen nach einem Hard Reset#

Du hast versehentlich git reset --hard HEAD~3 ausgeführt und drei Commits verloren. Sie sind direkt im Reflog:

bash
# Sieh, was du verloren hast
git reflog
 
# Der Commit vor dem Reset ist HEAD@{1}
git reset --hard f4e5d6c

Alle drei Commits sind zurück. Krise abgewendet.

Einen gelöschten Branch wiederherstellen#

Du hast einen Branch gelöscht, der ungemergte Arbeit hatte:

bash
git branch -D feature/experimental
# Oh nein, da steckten zwei Wochen Arbeit drin

Die Commits existieren noch. Finde sie:

bash
git reflog | grep "feature/experimental"
# Oder schau einfach durch das Reflog nach dem letzten Commit auf diesem Branch
 
# Gefunden. Branch an diesem Commit wiederherstellen:
git branch feature/experimental a1b2c3d

Der Branch ist zurück, mit allen seinen Commits.

Wiederherstellen nach einem schlechten Rebase#

Du hast rebased und alles ging schief. Konflikte überall, falsche Commits, Chaos:

bash
# Das Reflog zeigt, wo du vor dem Rebase warst
git reflog
# a1b2c3d HEAD@{0}: rebase (finish): ...
# ...
# f4e5d6c HEAD@{5}: rebase (start): checkout main
# b7a8c9d HEAD@{6}: commit: Your last good commit
 
# Zurück zum Zustand vor dem Rebase
git reset --hard b7a8c9d

Du bist genau da, wo du vor dem Rebase warst. Als wäre es nie passiert.

Das 30-Tage-Sicherheitsnetz#

Standardmäßig behält Git Reflog-Einträge für 30 Tage (90 Tage für erreichbare Commits). Danach können sie vom Garbage Collector entfernt werden. Du hast also einen Monat Zeit, um zu merken, dass du einen Fehler gemacht hast. In der Praxis ist das mehr als genug.

Du kannst den Ablauf prüfen:

bash
git config gc.reflogExpire
# Standard: 90.days.ago (für erreichbare)
git config gc.reflogExpireUnreachable
# Standard: 30.days.ago (für nicht erreichbare)

Wenn du paranoid bist, erhöhe es:

bash
git config --global gc.reflogExpireUnreachable "180.days.ago"

Eine persönliche Regel#

Vor jeder destruktiven Operation — Hard Reset, Force Push, Branch-Löschung — führe ich zuerst git log --oneline -10 aus. Ich merke mir mental den aktuellen HEAD. Es dauert zwei Sekunden und hat mich mehr als einmal vor einer Panik bewahrt, die ich nicht hätte haben müssen.

Stash richtig verwenden: Es ist nicht nur git stash#

Die meisten Leute benutzen Stash so:

bash
git stash
# etwas tun
git stash pop

Das funktioniert, aber es ist das Äquivalent davon, alles in eine Kiste mit der Aufschrift „Zeug" zu werfen. Wenn du drei Stashes hast, hast du keine Ahnung, welcher welcher ist.

Stashes benennen#

bash
git stash push -m "WIP: user auth form validation"

Jetzt, wenn du Stashes auflistest:

bash
git stash list
# stash@{0}: On feature/auth: WIP: user auth form validation
# stash@{1}: On main: Quick fix attempt for nav bug
# stash@{2}: On feature/payments: Experiment with Stripe webhooks

Du kannst genau sehen, was jeder Stash enthält.

Ungetrackte Dateien einschließen#

Standardmäßig stasht git stash nur getrackte Dateien. Neue Dateien, die du noch nicht hinzugefügt hast, bleiben zurück:

bash
# Alles stashen, einschließlich neuer Dateien
git stash push --include-untracked -m "WIP: new auth components"
 
# Oder sogar ignorierte Dateien einschließen (selten nötig)
git stash push --all -m "Full workspace snapshot"

Ich verwende --include-untracked fast jedes Mal. Neue Dateien zurückzulassen, wenn du Branches wechselst, verursacht Verwirrung.

Partielles Stashing#

Das ist das, was die meisten Leute nicht wissen. Du kannst bestimmte Dateien stashen:

bash
# Nur bestimmte Dateien stashen
git stash push -m "Just the auth changes" src/auth/ src/middleware.ts

Oder verwende den Patch-Modus, um bestimmte Hunks innerhalb von Dateien zu stashen:

bash
git stash push --patch -m "Partial: only the validation logic"

Git geht jede Änderung interaktiv durch und fragt, ob du sie stashen willst. y für ja, n für nein, s um den Hunk in kleinere Stücke aufzuteilen.

Apply vs Pop#

bash
# Pop: anwenden und aus der Stash-Liste entfernen
git stash pop stash@{2}
 
# Apply: anwenden aber in der Stash-Liste behalten
git stash apply stash@{2}

Ich verwende apply, wenn ich nicht sicher bin, ob der Stash sauber angewendet wird. Wenn es einen Konflikt gibt, bleibt der Stash erhalten. Bei pop, wenn es einen Konflikt gibt, bleibt der Stash auch in der Liste (viele Leute wissen das nicht), aber ich bevorzuge die explizite Absicht von apply.

Stash-Inhalte anzeigen#

bash
# Sehen, welche Dateien in einem Stash geändert wurden
git stash show stash@{0}
 
# Den vollständigen Diff sehen
git stash show -p stash@{0}

Einen Branch aus einem Stash erstellen#

Wenn dein Stash zu etwas Substanziellerem geworden ist:

bash
git stash branch feature/auth-validation stash@{0}

Das erstellt einen neuen Branch vom Commit, bei dem du ursprünglich gestasht hast, wendet den Stash an und verwirft ihn. Sauber.

Branching-Strategien: Welche tatsächlich funktioniert#

Es gibt drei Mainstream-Branching-Strategien. Jede hat einen Kontext, in dem sie glänzt, und Kontexte, in denen sie Schmerzen verursacht.

Gitflow#

Der Klassiker. main, develop, feature/*, release/*, hotfix/*. Erstellt von Vincent Driessen im Jahr 2010.

bash
# Feature-Branch
git checkout -b feature/user-auth develop
# ... arbeiten ...
git checkout develop
git merge --no-ff feature/user-auth
 
# Release-Branch
git checkout -b release/2.1.0 develop
# ... letzte Fixes ...
git checkout main
git merge --no-ff release/2.1.0
git tag -a v2.1.0 -m "Release 2.1.0"
git checkout develop
git merge --no-ff release/2.1.0
 
# Hotfix
git checkout -b hotfix/session-fix main
# ... fix ...
git checkout main
git merge --no-ff hotfix/session-fix
git checkout develop
git merge --no-ff hotfix/session-fix

Wann es funktioniert: Mobile Apps, Desktop-Software, alles mit benannten versionierten Releases und gleichzeitig unterstützten mehreren Versionen. Wenn du v2.1 und v3.0 auslieferst und beide patchen musst, handhabt Gitflow das.

Wann nicht: Webanwendungen mit Continuous Deployment. Wenn du 5-mal am Tag in die Produktion deployest, ist die Zeremonie von Release-Branches und Develop-Branches reiner Overhead. Die meisten Web-Teams, die Gitflow übernehmen, enden mit einem Develop-Branch, der dauerhaft kaputt ist, und Release-Branches, die niemand versteht.

GitHub Flow#

Einfach. Du hast main. Du erstellst Feature-Branches. Du öffnest PRs. Du mergst zu main. Du deployest main.

bash
git checkout -b feature/user-auth main
# ... arbeiten ...
git push origin feature/user-auth
# PR öffnen, reviewen lassen, mergen
# main ist immer deploybar

Wann es funktioniert: Kleine bis mittlere Teams, die Web-Apps ausliefern. Continuous Deployment. Wenn main immer deployed wird, ist das alles, was du brauchst. Es ist das, was diese Seite verwendet.

Wann nicht: Wenn du mehrere Release-Versionen pflegen musst, oder wenn du einen langen QA-Zyklus vor dem Deployment hast. GitHub Flow setzt voraus, dass main schnell in die Produktion geht.

Trunk-Based Development#

Alle committen direkt zu main (dem „Trunk") oder über sehr kurzlebige Branches (weniger als ein Tag). Keine lang laufenden Feature-Branches.

bash
# Kurzlebiger Branch (am selben Tag gemergt)
git checkout -b fix/auth-token main
# ... kleine, fokussierte Änderung ...
git push origin fix/auth-token
# PR reviewt und innerhalb von Stunden gemergt, nicht Tagen

Wann es funktioniert: Hochperformante Teams mit gutem CI/CD, umfassenden Testsuiten und Feature Flags. Google, Meta und die meisten großen Tech-Unternehmen verwenden Trunk-Based Development. Es erzwingt kleine, inkrementelle Änderungen und eliminiert die Merge-Hölle.

Wann nicht: Teams ohne gute Testabdeckung oder CI. Wenn das Mergen zum Trunk bedeutet, ungetesteten Code zu deployen, wirst du die Produktion ständig kaputt machen. Du brauchst auch Feature Flags für alles, das länger als einen Tag zum Bauen braucht:

bash
# Feature Flag im Code
if (featureFlags.isEnabled('new-checkout-flow')) {
  renderNewCheckout();
} else {
  renderLegacyCheckout();
}

Meine Empfehlung#

Für die meisten Webentwicklungsteams: Starte mit GitHub Flow. Es ist einfach, es funktioniert, und es erfordert kein Tooling über das hinaus, was GitHub/GitLab bereits bietet.

Wenn dein Team über 15-20 Entwickler wächst und du mehrmals am Tag deployest, schau dir Trunk-Based Development mit Feature Flags an. Die Investition in Feature-Flag-Infrastruktur zahlt sich aus durch weniger Merge-Konflikte und schnellere Iteration.

Wenn du versionierte Software auslieferst (Mobile Apps, CLI-Tools, Bibliotheken): Gitflow oder eine vereinfachte Version davon. Du brauchst tatsächlich diese Release-Branches.

Wähle keine Strategie, weil ein Blog-Post gesagt hat, sie wäre die beste. Wähle die, die zu dem passt, wie du tatsächlich auslieferst.

Git Hooks: Automatisiere die Dinge, die du ständig vergisst#

Git Hooks sind Scripts, die automatisch an bestimmten Punkten im Git-Workflow ausgeführt werden. Sie sind lokal auf deiner Maschine (nicht zum Remote gepusht), was bedeutet, du brauchst einen Weg, sie mit deinem Team zu teilen.

Die Hooks, die tatsächlich wichtig sind#

pre-commit — Läuft vor jedem Commit. Verwende es für Linting und Formatierung:

bash
#!/bin/bash
# .git/hooks/pre-commit
 
# ESLint nur auf gestagten Dateien ausführen
STAGED_FILES=$(git diff --cached --name-only --diff-filter=ACM | grep -E '\.(js|ts|tsx)$')
 
if [ -n "$STAGED_FILES" ]; then
  echo "Running ESLint on staged files..."
  npx eslint $STAGED_FILES --quiet
  if [ $? -ne 0 ]; then
    echo "ESLint failed. Fix errors before committing."
    exit 1
  fi
fi
 
# Prettier auf gestagten Dateien ausführen
if [ -n "$STAGED_FILES" ]; then
  echo "Running Prettier..."
  npx prettier --check $STAGED_FILES
  if [ $? -ne 0 ]; then
    echo "Prettier check failed. Run 'npx prettier --write' first."
    exit 1
  fi
fi

commit-msg — Validiert das Commit-Message-Format. Perfekt zum Erzwingen von Conventional Commits:

bash
#!/bin/bash
# .git/hooks/commit-msg
 
COMMIT_MSG=$(cat "$1")
PATTERN="^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .{1,72}"
 
if ! echo "$COMMIT_MSG" | grep -qE "$PATTERN"; then
  echo "Invalid commit message format."
  echo "Expected: type(scope): description"
  echo "Example: feat(auth): add session refresh endpoint"
  echo ""
  echo "Valid types: feat, fix, docs, style, refactor, test, chore, perf, ci, build, revert"
  exit 1
fi

pre-push — Läuft vor dem Pushen. Verwende es für Tests:

bash
#!/bin/bash
# .git/hooks/pre-push
 
echo "Running tests before push..."
npm test
if [ $? -ne 0 ]; then
  echo "Tests failed. Push aborted."
  exit 1
fi

Husky vs Native Hooks#

Native Hooks leben in .git/hooks/. Das Problem: Das .git-Verzeichnis wird nicht von Git getrackt, also kannst du Hooks nicht über das Repo teilen. Jeder muss sie manuell einrichten.

Husky löst das. Es speichert Hook-Konfigurationen im Repo und richtet .git/hooks automatisch bei npm install ein:

bash
npx husky init

Das erstellt ein .husky/-Verzeichnis. Füge Hooks als Dateien hinzu:

bash
# .husky/pre-commit
npx lint-staged

Kombiniert mit lint-staged bekommst du schnelle, gezielte Pre-Commit-Checks:

json
{
  "lint-staged": {
    "*.{js,ts,tsx}": ["eslint --fix", "prettier --write"],
    "*.css": ["prettier --write"],
    "*.json": ["prettier --write"]
  }
}

Das führt ESLint und Prettier nur auf den Dateien aus, die du tatsächlich commitest. Nicht die gesamte Codebase. Schnell.

Wann Hooks übersprungen werden#

Manchmal musst du ohne laufende Hooks committen. Notfall-Hotfixes, Work-in-Progress-Commits auf deinem eigenen Branch:

bash
git commit --no-verify -m "WIP: debugging production issue"

Verwende das sparsam. Wenn du dich dabei ertappst, Hooks regelmäßig zu überspringen, sind deine Hooks wahrscheinlich zu langsam oder zu streng.

Aliases, die Zeit sparen#

Meine .gitconfig hat sich über Jahre entwickelt. Das sind die Aliases, die überlebt haben — die, die ich tatsächlich täglich verwende, nicht die, die ich hinzugefügt habe, weil sie clever aussahen.

Schönes Log#

Das Standard-git log ist ausführlich. Das gibt dir eine saubere, farbige, graphenbasierte Ansicht:

bash
git config --global alias.lg "log --oneline --graph --all --decorate"

Verwendung:

bash
git lg
# * a1b2c3d (HEAD -> main) Fix session validation
# | * d4e5f6g (feature/payments) Add Stripe integration
# | * h7i8j9k Update payment models
# |/
# * l0m1n2o Merge PR #42
# * p3q4r5s Add user dashboard

Ich führe das 20-mal am Tag aus. Es ist der schnellste Weg, den Zustand deines Repositories zu verstehen.

Letzten Commit rückgängig machen#

Die Änderungen behalten, nur den Commit rückgängig machen:

bash
git config --global alias.undo "reset HEAD~1 --mixed"

Verwendung:

bash
git undo
# Commit ist weg, aber alle Änderungen sind noch in deinem Arbeitsverzeichnis

Ich verwende das, wenn ich zu früh committe, eine Datei vergesse oder die Änderungen umstrukturieren will.

Alles unstagen#

bash
git config --global alias.unstage "reset HEAD --"

Verwendung:

bash
git unstage src/auth/session.ts
# Datei ist ungestaged aber Änderungen sind erhalten

Alle Aliases auflisten#

Weil du vergessen wirst, was du eingerichtet hast:

bash
git config --global alias.aliases "config --get-regexp ^alias\\."

Weitere Aliases, die ich verwende#

bash
# Zeigen, was du gleich committest
git config --global alias.staged "diff --staged"
 
# Kurzer Status
git config --global alias.st "status -sb"
 
# Amend ohne Nachricht zu ändern
git config --global alias.amend "commit --amend --no-edit"
 
# Den letzten Commit zeigen
git config --global alias.last "log -1 HEAD --stat"
 
# Pull mit Rebase statt Merge
git config --global alias.up "pull --rebase --autostash"
 
# Branches löschen, die zu main gemergt wurden
git config --global alias.cleanup "!git branch --merged main | grep -v '^[ *]*main$' | xargs git branch -d"

Der up-Alias ist besonders gut. pull --rebase hält deine History linear, statt Merge-Commits für jeden Pull zu erstellen. --autostash stasht und restored deine Änderungen automatisch, wenn du schmutzige Dateien hast. Es ist das, was Pull standardmäßig hätte sein sollen.

Der cleanup-Alias löscht lokale Branches, die zu main gemergt wurden. Mit der Zeit sammelst du Dutzende veraltete Branches an. Führe das wöchentlich aus.

Die Befehle, die ich täglich verwende#

Das sind keine Aliases oder erweiterte Features. Es sind einfach Befehle, die ich ständig ausführe und die viele Entwickler nicht zu kennen scheinen.

Der ultimative Log-Befehl#

bash
git log --oneline --graph --all

Das zeigt jeden Branch, jeden Merge, die gesamte Topologie deines Repos in einer kompakten Ansicht. Es ist das Erste, was ich ausführe, wenn ich Änderungen pulle. Es beantwortet „Was passiert gerade in diesem Repo?"

Gestagte Änderungen diffen#

bash
git diff --staged

Das zeigt, was gleich committed wird. Nicht was in deinem Arbeitsverzeichnis geändert ist — was tatsächlich gestaged ist. Ich führe das vor jedem Commit aus. Immer. Es fängt versehentliche Einschlüsse, Debug-Statements, console.logs die nicht da sein sollten.

bash
# Nicht gestagte Änderungen sehen
git diff
 
# Gestagte Änderungen sehen
git diff --staged
 
# Beides sehen
git diff HEAD

Einen bestimmten Commit anzeigen#

bash
git show a1b2c3d

Zeigt den vollständigen Diff eines einzelnen Commits. Nützlich beim Reviewen der History, zum Verstehen was ein Commit tatsächlich geändert hat.

bash
# Nur die geänderten Dateien zeigen
git show --stat a1b2c3d
 
# Eine bestimmte Datei bei einem bestimmten Commit zeigen
git show a1b2c3d:src/auth/session.ts

Das Letzte ist unglaublich nützlich. Du kannst jede Datei an jedem Punkt der History anzeigen, ohne diesen Commit auszuchecken.

Blame mit Zeilenbereichen#

bash
git blame -L 42,60 src/auth/session.ts

Zeigt, wer die Zeilen 42-60 zuletzt geändert hat. Nützlicher als die gesamte Datei zu blamen, was normalerweise überwältigend ist.

bash
# Auch die Commit-Nachricht zeigen, nicht nur den Hash
git blame -L 42,60 --show-name src/auth/session.ts
 
# Whitespace-Änderungen ignorieren (sehr nützlich)
git blame -w -L 42,60 src/auth/session.ts
 
# Den Commit vor dem ge-blameten zeigen (tiefer graben)
git log --follow -p -- src/auth/session.ts

Das -w-Flag ist wichtig. Ohne es wird Blame Zeilen demjenigen zuordnen, der die Datei zuletzt reformatiert hat, was selten die Person ist, die du suchst.

Einen String über die gesamte History finden#

bash
# Finden, wann eine Funktion hinzugefügt oder entfernt wurde
git log -S "validateSession" --oneline
 
# Finden, wann ein Regex-Pattern auftauchte
git log -G "session.*timeout" --oneline

-S (die „Pickaxe") findet Commits, bei denen sich die Anzahl der Vorkommnisse eines Strings geändert hat. -G findet Commits, bei denen der Diff einem Regex entspricht. Beide sind mächtig für Archäologie — herauszufinden, wann etwas eingeführt oder entfernt wurde.

Zeigen, was sich zwischen zwei Punkten geändert hat#

bash
# Was sich zwischen zwei Branches geändert hat
git diff main..feature/auth
 
# Was sich auf diesem Branch geändert hat, seit er von main abzweigte
git diff main...feature/auth
 
# Nur die geänderten Dateien auflisten
git diff main...feature/auth --name-only
 
# Stat-Ansicht (Dateien + Einfügungen/Löschungen)
git diff main...feature/auth --stat

Zwei Punkte vs drei Punkte ist wichtig. Zwei Punkte zeigt den Unterschied zwischen den Spitzen beider Branches. Drei Punkte zeigt, was sich auf der rechten Seite geändert hat, seit sie von der linken Seite abzweigte. Drei Punkte ist normalerweise das, was du willst, wenn du einen Feature-Branch reviewst.

Ungetrackte Dateien aufräumen#

bash
# Sehen, was gelöscht werden würde (Trockenlauf)
git clean -n
 
# Ungetrackte Dateien löschen
git clean -f
 
# Ungetrackte Dateien und Verzeichnisse löschen
git clean -fd
 
# Ungetrackte und ignorierte Dateien löschen (nukleare Option)
git clean -fdx

Führe immer zuerst mit -n aus. git clean -fdx wird deine node_modules, .env, Build-Artefakte löschen — alles, was nicht von Git getrackt wird. Nützlich für einen wirklich frischen Start, aber destruktiv.

Eine einzelne Datei von einem anderen Branch wiederherstellen#

bash
# Die main-Branch-Version einer Datei holen, ohne Branches zu wechseln
git restore --source main -- src/config/database.ts

Oder von einem bestimmten Commit:

bash
git restore --source a1b2c3d -- src/config/database.ts

Das ist sauberer als die alte git checkout main -- path/to/file-Syntax, und es beeinflusst HEAD nicht.

Alles zusammen: Ein echter Workflow#

So sieht ein typischer Tag mit diesen Tools aus:

bash
# Morgens: prüfen, was passiert
git lg
git fetch --all
 
# Ein Feature starten
git checkout -b feature/session-refresh main
 
# Arbeiten, inkrementell committen
git add -p                        # Bestimmte Hunks stagen, nicht ganze Dateien
git commit -m "Add token refresh endpoint"
git commit -m "Add refresh token rotation"
git commit -m "Fix: handle expired refresh tokens"
git commit -m "Add integration tests"
 
# Unterbrochen: einen Produktions-Bug fixen
git worktree add ../hotfix main
cd ../hotfix
# ... fix, commit, push, PR gemergt ...
cd ../my-project
git worktree remove ../hotfix
 
# Zurück zur Feature-Arbeit
git commit -m "Fix edge case in token validation"
 
# Bereit für PR: History aufräumen
git rebase -i main
# Fix-Commits squashen, für Klarheit umformulieren
 
# Pushen und PR öffnen
git push -u origin feature/session-refresh
 
# Etwas im Staging kaputt? Herausfinden, welcher Commit:
git bisect start
git bisect bad HEAD
git bisect good main
git bisect run npm test
 
# Ups, ich habe das Falsche hard-resettet
git reflog
git reset --hard HEAD@{2}

Jeder dieser Befehle dauert Sekunden. Zusammen sparen sie Stunden. Nicht hypothetische Stunden — echte Stunden, jede Woche, die ich sonst damit verbringen würde, Git-Chaos zu entwirren, manuell nach Bugs zu suchen oder Arbeit durch Kontextwechsel zu verlieren.

Git ist ein Werkzeug, das Tiefe belohnt. Die Grundlagen bringen dich durch den Tag. Aber die Befehle in diesem Beitrag sind das, was „Ich benutze Git" von „Git macht mich tatsächlich schneller" trennt. Lerne sie inkrementell. Nimm dir diese Woche eine neue Technik vor und verwende sie, bis sie in Fleisch und Blut übergegangen ist. Dann nimm die nächste.

Dein zukünftiges Ich, das um 23 Uhr auf einen Produktions-Bug starrt, wird dir dankbar sein, dass du git bisect kennst.

Ähnliche Beiträge