6 Punkte von GN⁺ 2024-05-27 | 1 Kommentare | Auf WhatsApp teilen
  • Ein PDF, das häufige Stolperstellen in Git nach Commit-Erstellung, Branch-Wechsel, Vergleich von Änderungen, Remote-Synchronisierung und Befehlen zum Verwerfen von Änderungen schnell auffindbar zusammenfasst
  • Der lokale Workflow dreht sich um git status, git add, git commit und git diff, um den Zustand vor und nach dem Staging zu prüfen und Commits zu erstellen
  • Branch-Arbeiten werden mit git switch, git checkout, git branch, git merge, git rebase und git cherry-pick für Erstellen, Wechseln, Mergen und Rebasing erledigt
  • Für das Durchsuchen der Historie und die Wiederherstellung sind git log, git blame, git show, git reflog, git reset und git restore enthalten; auch Branches, Tags, HEAD und Remote-Branches können als Commit-Referenzen verwendet werden
  • Remote-Repository-Arbeiten behandeln mit git remote add, git fetch, git pull, git push, git push --force-with-lease und git push --tags die Abläufe zum Holen und Senden von Änderungen

Repository starten und aktuellen Status prüfen

  • Neues Repository starten:
    • git init
  • Bestehendes Repository klonen:
    • git clone $URL
  • Aktuelle Position und Status prüfen:
    • git status

Commit vorbereiten und erstellen

  • Nicht getrackte Datei hinzufügen:
    • git add $FILE
  • Alle nicht getrackten Dateien und nicht gestagten Änderungen hinzufügen:
    • git add .
  • Nur ausgewählte Teiländerungen einer Datei stagen:
    • git add -p
  • Datei löschen oder verschieben:
    • git rm $FILE
    • git mv $OLD $NEW
  • Datei aus dem Git-Tracking entfernen, ohne sie zu löschen:
    • git rm --cached $FILE
  • Alles unstagen:
    • git reset HEAD
  • Commit erstellen:
    • git commit
    • Öffnet einen Texteditor zum Schreiben der Nachricht
  • Commit mit direkt angegebener Nachricht:
    • git commit -m 'message'
  • Commit inklusive nicht gestagter Änderungen mit Nachricht:
    • git commit -am 'message'

Branch wechseln und verwalten

  • Branch wechseln:
    • git checkout $NAME
    • git switch $NAME
  • Branch erstellen:
    • git checkout -b $NAME
    • git switch -c $NAME
  • Branch-Liste anzeigen:
    • git branch
  • Branches nach den neuesten Commits sortieren:
    • git branch --sort=-committerdate
  • Branch löschen:
    • git branch -d $NAME
  • Branch zwangsweise löschen:
    • git branch -D $NAME

Historie durchsuchen und Code nachverfolgen

  • Branch-Historie anzeigen:
    • git log main
  • Beziehung zweier Branches als Graph anzeigen:
    • git log --graph a b
  • Einzeiliges Log anzeigen:
    • git log --oneline
  • Prüfen, wer jede Zeile einer Datei zuletzt geändert hat:
    • git blame $FILENAME
  • Alle Commits anzeigen, die eine Datei geändert haben:
    • git log $FILENAME
  • Commits finden, die einen bestimmten Text hinzugefügt oder entfernt haben:
    • git log -S banana

Änderungen vergleichen

  • Unterschiede zwischen Commit und Parent-Commit anzeigen:
    • git show $COMMIT_ID
  • Unterschiede zwischen einem Merge-Commit und den gemergten Parents anzeigen:
    • git show --remerge-diff $COMMIT_ID
  • Diff-Zusammenfassung anzeigen:
    • git diff $COMMIT_ID --stat
    • git show $COMMIT_ID --stat
  • Zwei Commits vergleichen:
    • git diff $COMMIT_ID $COMMIT_ID
  • Nur eine bestimmte Datei mit einem Commit vergleichen:
    • git diff $COMMIT_ID $FILENAME
  • Alle gestagten und nicht gestagten Änderungen vergleichen:
    • git diff HEAD
  • Nur gestagte Änderungen vergleichen:
    • git diff --staged
  • Nur nicht gestagte Änderungen vergleichen:
    • git diff

Konfigurationsdateien und Optionen

  • Lokale Git-Konfigurationsdatei:
    • .git/config
  • Globale Git-Konfigurationsdatei:
    • ~/.gitconfig
  • Liste zu ignorierender Dateien:
    • .gitignore
  • Konfigurationsoption setzen:
    • git config user.name 'Julia'
  • Globale Option setzen:
    • git config --global ...
  • Alias hinzufügen:
    • git config alias.st status
  • Mögliche Konfigurationsoptionen prüfen:
    • man git-config

Historie ändern und Dateien wiederherstellen

  • Den neuesten Commit zurücksetzen, Arbeitsverzeichnis unverändert lassen:
    • git reset HEAD^
  • Fehlgeschlagenes Rebase zurücksetzen:
    • git reflog BRANCHNAME
    • git reset --hard $COMMIT_ID
  • Die letzten 5 Commits zu einem zusammenfassen:
    • git rebase -i HEAD^^^^^^
    • Bei den zusammenzufassenden Commits pick in fixup ändern
  • Dateiversion aus einem anderen Branch oder Commit holen:
    • git checkout $COMMIT_ID $FILE
    • git restore $FILE --source $COMMIT_ID
  • Commit-Nachricht ändern oder vergessene Datei hinzufügen:
    • git commit --amend

Branches zusammenführen

  • Fast-Forward-Merge, der den aktuellen Branch mit einem anderen Branch aktualisiert:
    • git switch main
    • git merge banana
  • Einen Commit in einen anderen Branch kopieren:
    • git cherry-pick $COMMIT_ID
  • Auseinanderlaufende Branches per Rebase zusammenführen:
    • git switch banana
    • git rebase main
  • Auseinanderlaufende Branches per Merge zusammenführen:
    • git switch main
    • git merge banana
    • git commit
  • Auseinanderlaufende Branches per Squash-Merge zusammenführen:
    • git switch main
    • git merge --squash banana
    • git commit

Remote-Repository und Synchronisierung

  • Remote-Repository hinzufügen:
    • git remote add $NAME $URL
  • Branch main zum Remote origin pushen:
    • git push origin main
  • Einen Branch beim ersten Push zu origin hochladen und Tracking-Branch setzen:
    • git push -u origin $NAME
  • Aktuellen Branch zum Remote-Tracking-Branch pushen:
    • git push
  • Force-Push:
    • git push --force-with-lease
  • Tags pushen:
    • git push --tags
  • Änderungen holen, ohne lokale Branches zu ändern:
    • git fetch origin main
  • Alle Branches fetchen:
    • git fetch --all
  • Nach dem Fetch auf den aktuellen Branch rebasen:
    • git pull --rebase
  • Nach dem Fetch in den aktuellen Branch mergen:
    • git pull origin main
    • git pull

Wie man auf Commits verweist

  • An der Stelle von $COMMIT_ID können verschiedene Referenzen verwendet werden:
    • Branch: main
    • Tag: v0.1
    • Commit-ID: 3e887ab
    • Remote-Branch: origin/main
    • Aktueller Commit: HEAD
    • Commit vor 3 Commits: HEAD^^^
    • Commit vor 3 Commits: HEAD~3

Änderungen verwerfen

  • Sowohl gestagte als auch nicht gestagte Änderungen an einer Datei löschen:
    • git checkout HEAD $FILE
  • Nicht gestagte Änderungen an einer Datei löschen:
    • git checkout $FILE
  • Alle gestagten und nicht gestagten Änderungen löschen:
    • git reset --hard
  • Gestagte und nicht gestagte Änderungen stashen:
    • git stash
  • Nicht getrackte Dateien löschen:
    • git clean

1 Kommentare

 
GN⁺ 2024-05-27
Hacker-News-Kommentare
  • Es gibt ein paar versteckte Perlen unter den Befehlen: git diff --staged zeigt den Unterschied zwischen dem letzten Commit und dem aktuell gestagten Inhalt. Das ist praktisch, um beim feingranularen Stagen von Dateien oder Hunks zu prüfen, ob wirklich nur das Gewollte enthalten ist.
    git log <file> zeigt nur die Commits an, in denen die betreffende Datei geändert wurde. So muss man sich nicht durch das komplette git log wühlen, was besonders bei Dateien nützlich ist, die sich nicht oft ändern.
    Wenn man bei git add --patch noch feiner auswählen möchte als die von Git aufgeteilten Hunks, sollte man die Option e ausprobieren. Dabei wird erklärt, wie man einzelne Zeilen ein- oder ausschließt, sodass man es sich nicht merken muss.

    • Ich habe gerade erst gelernt, dass git diff --staged ein Alias für git diff --cached ist. Ich verwende cached schon seit Jahren, aber staged ist definitiv intuitiver. Ich weiß nur nicht, ob ich dieses Muskelgedächtnis wieder loswerde.
    • Wenn man sich für solche Befehle viele Aliasse anlegt, spart das einiges an Zeit und senkt die Hürde, die Arbeit besser zu organisieren.
      Wer Interesse hat, kann hier schauen oder Aliasse vorschlagen: https://gitlab.com/Falimonda/gitrc
    • Wenn man git config --global commit.verbose true setzt, fügt Git im Kommentarbereich des Commit-Message-Editors automatisch den gestagten Diff ein.
  • Einer meiner Lieblingsbefehle, der nicht auf der Liste steht, ist git log -LstartLine,endLine:fileName.
    Das ist wie ein erweitertes git blame: Es zeigt, wie sich die angegebenen Zeilen im Lauf der Zeit verändert haben und wer sie geändert hat. Dabei wird die Verfolgung auch fortgesetzt, wenn sich Zeilennummern ändern oder die Datei verschoben wird.
    Es beantwortet Fragen wie „Wer hat das wann und warum so gemacht?“ oder „Seit wann ist das so?“.

  • Das erinnert mich an das Zitat von Phil Karlton: „In der Informatik gibt es nur zwei harte Probleme: Cache-Invalidierung und Benennung.“
    Aus dieser Perspektive fand ich den Git-Befehlsnamen blame immer ziemlich seltsam, und offenbar ging das nicht nur mir so.
    What does 'git blame' do? https://stackoverflow.com/questions/31203001/what-does-git-blame-do
    Blame someone else for your bad code https://news.ycombinator.com/item?id=27963868
    Git blame should be called git credit https://dev.to/damcosset/git-blame-should-be-called-git-credit-27h5
    Does Git Blame sound too negative? https://www.reddit.com/r/ProgrammerHumor/comments/r5lzyo/does_git_blame_sound_too_negative_just_look_up/

    • Wenn man den Namen des Tools git bedenkt, wirkt blame wie ein markengerechter Name.
    • Stimme zu. Ich habe in meiner Konfiguration blame in who umbenannt. Da ich allerdings ein eingefleischter Vim-Nutzer bin, verwende ich eigentlich einfach w.
  • Mir gefiel die Art, wie ich Git gelernt habe. Ich habe zunächst mit dem Third-Party-Git-GUI-Tool Sourcetree angefangen, das sehr leicht zu benutzen und zu verstehen war.
    Danach bin ich zu git gui gewechselt, aber diesen Schritt kann man auch überspringen. Später wurde es mir lästig, jedes Mal eine UI zu öffnen, also habe ich Terminal-Befehle gelernt.
    Ich würde das Git-Anfängern empfehlen, aber das ist natürlich nur meine n=1-Erfahrung.

    • Ich kenne von Git nur clone, pull, commit und push, und bisher hat das völlig gereicht. Git kann tatsächlich so einfach sein. Natürlich kann man bei Bedarf auch komplexere Nutzungsweisen lernen.
    • Ich möchte nicht mehr zu Terminal-Befehlen zurück. Git-Repositories und Workflows versteht man viel besser, wenn sie visualisiert sind, deshalb ist Git praktisch ein Tool, das nach einer GUI verlangt.
      In dem Moment, in dem ich Magit zu verwenden begann, fing ich an, Git zu verstehen.
    • Ich bin von Sourcetree zu Fork gewechselt. Es ist sehr ähnlich, aber deutlich performanter und die UI fühlt sich flüssiger an. Vorausgesetzt natürlich, Sourcetree hat sich in den letzten Jahren nicht verbessert.
    • Mein Weg war ähnlich, aber ich habe GitKraken und Sublime Merge verwendet, und weil es auch komplexe Aufgaben leicht macht, habe ich Sublime Merge behalten.
      Über Sublime Merge kann ich nur Gutes sagen.
  • Ich frage mich, ob Git aus historischen Gründen unnötig komplex ist.
    Wenn ich dieses Cheat Sheet sehe, wird mir diese Komplexität noch bewusster.

    • Ja. Git wurde von Linus Torvalds gebaut, um die Entwicklung des Linux-Kernels zu ermöglichen und voranzubringen.
      Dass es so weit verbreitet wurde, ist vielleicht ein „glücklicher Zufall“ oder auch ein Unglück. GitHub hat großen Anteil daran, Git durch „kostenloses Git-Hosting“ zu popularisieren, aber ich halte Git nicht für das ideale Versionsverwaltungssystem für viele Projekte.
      Weil es für Linus und Linux gebaut wurde, wirken Terminologie und Workflow manchmal umgekehrt zu gewöhnlichen Versionsverwaltungssystemen. Der Name Pull Request kommt ebenfalls aus der Linux-Entwicklung, weil Linus oder die Maintainer Änderungen in den Kernel-Tree hineinziehen. In anderen Systemen nennt man das meist Merge Request, und das ist viel logischer, weil Änderungen unabhängig von ihrer Herkunft in den Tree zusammengeführt werden.
      Git war fast das einzige Open-Source-verteilte Versionsverwaltungssystem, das mit riesigen Codebasen schnell umgehen konnte, und dadurch wurde es auch in Großunternehmen durch Trägheit zum Standard.
      Für persönliche Projekte bevorzuge ich Mercurial deutlich und nutze es auch tatsächlich, aber wenn man Hg auf einer großen Codebasis wie Android AOSP verwendet, ruckelt es extrem. Mercurial hat die bessere Benutzeroberfläche und Befehlsstruktur und kann auch spontan einen Webserver starten, um den Änderungsbaum anzuzeigen, aber da es in Python geschrieben ist, sind Performance und Skalierbarkeit nicht ideal.
    • Ein Teil der Komplexität kommt daher, dass es in frühen Versionen ziemlich teuer war, neue Unterbefehle hinzuzufügen. Dadurch wurden bestehende Unterbefehle wie git checkout immer weiter mit Funktionen überladen, was die User Experience noch verwirrender machte.
      In aktuellem Git sind die Kosten für neue Unterbefehle gesunken, und man hat begonnen, spezifischere Befehle wie git switch hinzuzufügen.
    • Für die meisten Workflows reicht es meiner Meinung nach, ein paar Git-Befehle wie log, add, commit, pull und push zu kennen. Es gibt ja auch GUI-Tools.
      Ehrlich gesagt finde ich Git für normale Arbeit gar nicht so komplex. Es hat nur viele mächtige Funktionen, die man bei Bedarf nutzen kann.
    • Git ist nicht unnötig komplex. Der Workflow, den Git unterstützen sollte, war selbst außergewöhnlich komplex, und die Zielnutzer, die Kernel-Entwickler, waren sehr technisch, sodass Komplexität kein großes Problem war.
      Kernel-Entwickler mussten dezentral miteinander kommunizieren und auf der Arbeit anderer weiterarbeiten. Wie arbeitet man in einem Projekt, an dem 15.000 Menschen Code schreiben? Genau dafür wurde Git gebaut.
      Es ist so komplex, wie es sein muss, um den Workflow zu unterstützen, für den es entworfen wurde. Ein einfacheres Werkzeug zu bauen ist leicht, aber dann könnte es komplexe Workflows wie die Entwicklung des Linux-Kernels nicht unterstützen. Es ist also nicht unnötig komplex, aber für einfache Anwendungsfälle definitiv komplexer als nötig.
      Wenn man ein weniger komplexes Frontend braucht, gibt es viele Tools, die den Umgang mit Git erleichtern. Wenn man kein hauptberuflicher Softwareentwickler ist, würde ich nicht empfehlen, Git direkt zu benutzen. Wenn man aber Vollzeitentwickler ist, lohnt es sich, Zeit in das Verständnis der internen Konzepte zu investieren, und sobald man sie verstanden hat, ist Git im Verhältnis zu dem, was es leistet, ziemlich elegant.
      Ich würde niemandem den Status als Softwareentwickler absprechen, nur weil er Git nicht kennt, aber wenn es einem keinen Dopaminschub gibt, selbst herauszufinden, wie solche grundlegenden Systeme funktionieren, dann ist dieses Feld vielleicht einfach nicht das Richtige. Das ist aber auch okay; nicht alles passt zu jedem.
      Um Git-Navigation zu lernen, empfehle ich https://learngitbranching.js.org. Natürlich gilt auch https://xkcd.com/1597/.
    • Gutes UI-Design ist eine eigene Disziplin und erfordert zusätzlichen Aufwand, deshalb wird es meistens einfach ignoriert.
  • Ich kannte git add -p nicht, aber es ist wirklich nützlich.
    -p, --patch ermöglicht es, interaktiv Patch-Hunks zwischen Index und Working Tree auszuwählen und dem Index hinzuzufügen. So bekommt man die Gelegenheit, Unterschiede zu prüfen, bevor man Änderungen in den Index aufnimmt.

    • Wenn möglich, wähle ich die Hunks der aktuellen Arbeit direkt aus und setze daraus konsistente Commits zusammen. Das hilft mir, im Kopf noch einmal zu ordnen, was ich getan habe, und wenn ich es jemandem erklären muss, kann ich dem Commit-Verlauf folgen und es daran entlang erklären.
  • Was ich hier noch ergänzen möchte, ist die Kombination aus git commit --fixup $COMMIT_ID und git rebase -i upstream/master --autosquash.
    Das ist in meinem aktuellen Workflow unverzichtbar geworden. Im Julia-Beispiel wird HEAD^^^^^ verwendet, um die letzten fünf Commits zu rebaseden, aber ich habe bis vor Kurzem HEAD~5 benutzt. Jetzt habe ich gelernt, dass man einfach alle Commits bis zum upstream-HEAD rebaseden kann.

    • git commit --fixup $COMMIT_ID muss ich sofort übernehmen. Ich habe immer kleine Korrektur-Commits wie „oops GET users/me“ erstellt und sie beim nächsten interaktiven Rebase manuell verschoben und als Fixup markiert, aber diese Methode ist viel besser.
    • Das habe ich auch erst kürzlich gelernt, und es ist großartig, wenn man in Gerrit mit gestapelten Patches arbeitet.
  • „Die letzten fünf Commits zu einem zusammenfassen: git rebase -i HEAD^^^^^^“ — ich glaube, git rebase -i HEAD~5 ist korrekt.
    Vorausgesetzt natürlich, dass man nur eine lineare Historie rebasen will.
    https://stackoverflow.com/questions/2221658/whats-the-difference-between-head-and-head-in-git

  • Die größte Erkenntnis war der Moment, in dem ich verstanden habe, dass alle Commits immer noch intern vorhanden sind. Tags und Branches zeigen nur auf Commits, und auch die Commits, auf die ein Branch früher gezeigt hat, sind nicht verschwunden.
    Viele Befehle — wahrscheinlich fast alle — sind nicht-destruktiv, sodass man frühere Zustände eines Branches leicht referenzieren kann.
    git reflog hatte ich schon gesehen, aber ich habe es meist nur benutzt, um mich zu erinnern: „Wie hieß noch mal dieser Branch, an dem ich früher gearbeitet habe?“
    Ich hatte eine halbwegs gesunde Angst vor Aktionen, mit denen man mitten in eine unerwünschte Konfliktauflösung geraten kann. Meist lag das daran, dass ich versehentlich einen falschen Commit oder Branch referenziert hatte und dadurch Konflikte entstanden.
    Diese Angst ist nicht völlig verschwunden, aber zumindest beim Wiederherstellen früherer Arbeit zwischen Vorgängen wie Rebase bin ich inzwischen deutlich entspannter.

    • Jujutsu hat diese Angst komplett beseitigt.
      Wenn ich nicht weiß, ob nach einer Aktion Konflikte auftreten, probiere ich es einfach aus und sehe nach. Wenn es Konflikte gibt, löse ich sie, mache die Aktion rückgängig, die den Konflikt erzeugt hat, oder erstelle anderswo einen neuen Commit und lasse die Konflikte erst einmal für später liegen.
      Wenn ich weiß, dass es Konflikte geben wird, dupliziere ich den betreffenden Branch und gleiche es vorab ab oder führe die konfliktverursachende Aktion aus und gehe den Lösungsprozess durch. Wenn die Auflösung unübersichtlich wird, mache ich einen Teil der Lösung wieder rückgängig und versuche weiter, schiebe es vorübergehend auf und arbeite an etwas anderem oder verwerfe den gesamten Tree.
  • Ganz offen gesagt: Man sollte Git beiseitelegen und sich JJ ansehen.
    https://martinvonz.github.io/jj/v0.17.1/
    Ich könnte mir vorstellen, dass sogar Linus die Git-UI für miserabel hält. JJ hat einen hervorragenden Workflow.

    • Mit dem fehlenden Index kann ich mich schwer anfreunden. Für manche mag das ein einfacherer Workflow sein, aber ich bin — im Guten wie im Schlechten — zu sehr an den Git-Index gewöhnt, als dass sich meine Arbeitsweise nicht ziemlich stark ändern würde.
      Allerdings habe ich es außer ein wenig Herumprobieren noch nicht wirklich intensiv in echter Arbeit genutzt, daher will ich mich nicht zu endgültig festlegen.