6 Punkte von GN⁺ 2023-11-03 | 1 Kommentare | Auf WhatsApp teilen
  • Selbst wenn man Git lange benutzt, gibt es viele überlappende und wiederverwendete Begriffe, sodass es schwer ist, das tatsächliche Verhalten allein durch das Auswendiglernen von Befehlen vorherzusagen
  • Dieselben Wörter und Symbole wie HEAD, heads, detached HEAD, ours/theirs, ../... ändern je nach Kontext ihre Bedeutung
  • index, staged und cached hängen alle mit .git/index zusammen, aber reset, revert und restore haben trotz ähnlicher Namen unterschiedliche Auswirkungen auf Commit-Historie, Working Tree und Branch-Pointer
  • Dass main einen Remote trackt, ist etwas anderes als ein Remote-Tracking-Branch wie origin/main, und checkout vereint Branch-Wechsel und Dateiwiederherstellung in einem einzigen Befehl
  • Um Git zu verstehen, muss man neben einzelnen Befehlen auch das interne Modell betrachten, in dem Branches, Referenzen, Commits, Index und Remote-Tracking-Status zusammenwirken

HEAD, Branches, detached HEAD

  • In Git bezeichnet heads die Branches; intern werden Branches im Verzeichnis .git/refs/heads gespeichert
    • Das offizielle Git-Glossar unterscheidet zwischen branch als allen Commits darüber und head als dem neuesten Commit, man kann es aber auch als zwei Perspektiven auf dasselbe Objekt sehen
  • HEAD zeigt auf den aktuellen Branch und wird in .git/HEAD gespeichert
    • Schon die Formulierung „head ist ein Branch und HEAD ist der aktuelle Branch“ zeigt gut, wie verwirrend die Git-Terminologie ist
  • Der detached HEAD state ist ein Zustand, in dem HEAD nicht auf einen Branch, sondern direkt auf eine Commit-ID zeigt
    • Wenn man einen Tag auscheckt, kann man in einen detached HEAD geraten, weil ein Tag kein Branch ist
    • In diesem Zustand funktioniert git pull nicht, und git push ebenfalls nur auf besondere Weise
    • git commit, git merge, git rebase und git cherry-pick sind möglich, aber neue Commits sind mit keinem Branch verbunden und können dadurch schwer wiederzufinden sein
    • Man verlässt den detached-HEAD-Zustand, indem man einen neuen Branch erstellt oder zu einem bestehenden Branch wechselt

ours und theirs in merge und rebase

  • Bei der Konfliktauflösung wählt git checkout --ours file.txt die Datei von „ours“, aber der Bezugspunkt ist bei merge und rebase unterschiedlich
  • Bei merge ist der aktuelle Branch ours, der hereingemergte Branch ist theirs
    • Beispiel: Wenn man git checkout merge-into-ours und danach git merge from-theirs ausführt, ist der aktuelle Branch ours
  • Bei rebase ist der aktuelle Branch theirs, der Ziel-Branch des rebase ist ours
    • Beispiel: Wenn man git checkout theirs und danach git rebase ours ausführt, wird der Ziel-Branch zu ours
  • Dieser Unterschied hängt damit zusammen, dass git rebase main intern die Commits des aktuellen Branches wiederholt in eine Kopie des main-Branches hineinmerged
  • ourstheirs erklärt die Begriffe ours und theirs kurz
    • Auch in VSCode können „current change“ und „incoming change“ auf ähnliche Weise verwirren

Remote-Statusmeldungen und fast-forward

  • Your branch is up to date with 'origin/main' bedeutet nicht, dass das aktuelle main auf dem neuesten Stand des Remote ist
    • Es bedeutet, dass der lokale Branch mit origin/main übereinstimmt, wie es beim letzten git fetch oder git pull gespeichert wurde
    • Wenn der letzte fetch vor 5 Tagen war, ist der Stand also nur „nach dem Stand von vor 5 Tagen“ aktuell
  • Git speichert Informationen über den Zeitpunkt des letzten fetch im reflog, aber in der Statusmeldung wird die Zeit „bezogen auf den letzten fetch“ nicht angezeigt
  • can be fast-forwarded bedeutet, dass der Remote-Branch weiter vorne liegt als der lokale Branch und lokal keine zusätzlichen Commits existieren, sodass nur der Pointer nach vorn verschoben werden kann
    • Beispiel: Wenn main A-B-C ist und origin/main A-B-C-D-E, dann sind nach git pull beide A-B-C-D-E
    • In diesem Fall kann es keinen Merge-Konflikt geben
  • Wenn fast-forward nicht möglich ist, sind beide Branches voneinander abgewichen
    • Beispiel: Wenn main einen Commit X hat, den origin/main nicht hat, und origin/main die Commits D und E hat, die main nicht hat, dann ist fast-forward unmöglich
    • In diesem Fall zeigt Git an, dass die beiden Branches diverged sind und jeweils eine unterschiedliche Anzahl eigener Commits haben

Syntax zur Commit-Auswahl: ^, ~, .., ...

  • HEAD^ und HEAD~ zeigen beide auf den einen vorherigen Commit
    • HEAD^^^, HEAD~~~ und HEAD~3 zeigen alle auf den Commit drei Schritte zurück
  • Bei einem Merge-Commit mit mehreren Elternteilen ändert sich die Bedeutung von HEAD^2 und HEAD^3
    • HEAD^ ist der erste Eltern-Commit
    • HEAD^2 ist der zweite Eltern-Commit
    • HEAD^3 ist der dritte Eltern-Commit
    • HEAD~3 bedeutet der Eltern-Commit des Eltern-Commits des Eltern-Commits und ist daher nicht dasselbe wie HEAD^3
  • git log main..test und git log main...test verhalten sich ebenfalls unterschiedlich
    • main..test zeigt die Commits, die nur in test vorhanden sind
    • test..main zeigt die Commits, die nur in main vorhanden sind
    • main...test zeigt die Commits, die jeweils nur auf einer Seite vorhanden sind
  • In git diff werden .. und ... anders interpretiert als in git log
    • git diff test..main vergleicht unter Einbeziehung sowohl der Änderungen auf test als auch auf main
    • git diff test...main vergleicht den gemeinsamen Vorfahren mit dem Ende von main und zeigt nur einen einseitigen diff
  • Weitere Informationen zur Verwirrung rund um die Punktnotation gibt es auch in pain in dots

reference, refspec, tree-ish

  • In Git wird reference in mehreren Bedeutungen verwendet
    • Branches und Tags wie main oder v0.2
    • HEAD, das auf den aktuellen Branch zeigt
    • Ausdrücke wie HEAD^^^, die zu einer Commit-ID aufgelöst werden
  • Git-Dokumentation nennt Ausdrücke wie HEAD^^^ streng genommen „revision parameters“, im Alltag können sie aber wie Referenzen behandelt werden
  • Eine symbolic reference ist eine Referenz, die auf eine andere Referenz zeigt, wie etwa HEAD
    • HEAD ist ein zentrales Konzept, das das Verhalten der Git-Kernbefehle stark beeinflusst
  • Die Remote-Konfiguration in .git/config enthält refspecs
    • Beispiel: +refs/heads/main:refs/remotes/origin/main
    • Oft verwendet man einfach den Wert, den git clone oder git remote add standardmäßig anlegt
  • tree-ish bezeichnet in git checkout THING . alles, was THING sein kann
    • eine Commit-ID
    • eine Commit-Referenz wie main, HEAD^^ oder v0.3.2
    • ein Unterverzeichnis innerhalb eines Commits wie main:./docs
    • In der Praxis reicht oft die Faustregel „ein Commit oder eine Commit-Referenz“

index, staged, cached

  • index, staged und cached hängen alle mit .git/index zusammen
    • .git/index ist die Datei, die verwendet wird, wenn Änderungen mit git add gestaged werden
  • Verschiedene Ausdrücke für dasselbe Objekt sind über mehrere Befehle verstreut
    • git diff --cached
    • git rm --cached
    • git diff --staged
    • .git/index
  • Allerdings haben die Flags --index und --cached nicht immer dieselbe Bedeutung
    • Ein Beitrag des Git-Lead-Maintainers Junio Hamano erklärt die Unterschiede im Detail
  • Aus Performance-Gründen listet der Index auch untracked files auf, aber normalerweise versteht man die staging area nicht so, dass sie untracked files einschließt

reset, revert, restore

  • reset, revert und restore klingen ähnlich, betreffen in Git aber unterschiedliche Ziele
  • git revert COMMIT erstellt auf dem aktuellen Branch einen neuen Commit, der die umgekehrte Änderung von COMMIT ausführt
    • Wenn COMMIT drei Zeilen hinzugefügt hat, löscht der neue Commit diese drei Zeilen
  • git reset --hard COMMIT setzt den aktuellen Branch gewaltsam auf den Stand von COMMIT zurück
    • Das ist ein sehr gefährlicher Vorgang, weil dadurch alle neuen Änderungen nach COMMIT gelöscht werden
  • git restore --source=COMMIT PATH stellt die Dateien in PATH auf den Zustand zum Zeitpunkt von COMMIT zurück
    • Andere Dateien oder die Commit-Historie werden nicht verändert
  • git reset --hard und git restore . verhalten sich allein verwendet oft ähnlich, aber git reset --hard COMMIT und git restore --source COMMIT . verhalten sich völlig unterschiedlich

track, Remote-Tracking-Branch, untracked files

  • Git verwendet das Wort track auf drei verschiedene Arten
    • Untracked files: in git status meint Dateien, die Git nicht verwaltet und die nicht in einem Commit enthalten sind
    • Ein Remote-Tracking-Branch wie origin/main ist eine lokale Referenz, die die Commit-ID enthält, auf die main im Remote origin beim letzten git pull oder git fetch gezeigt hat
    • Ein lokaler Branch kann so konfiguriert sein, dass er einen Remote-Branch trackt, wie in „branch foo set up to track remote branch bar from origin“
  • Zu sagen, main sei ein Branch, der einen Remote trackt, ist etwas anderes als zu sagen, origin/main sei ein Remote-Tracking-Branch
  • main ist ein echter Branch, daher kann man darauf Commits erstellen und Merges durchführen
    • Wenn in .git/config eingestellt ist, dass er den Remote-main trackt, kann man git pull und git push bequemer verwenden
  • origin/main ist trotz des Namens „Remote-Tracking-Branch“ kein normaler Branch
    • Man kann nicht direkt darauf committen
    • Er wird nur aktualisiert, wenn der neueste Stand von main aus dem Remote mit git pull oder git fetch geholt wird

checkout, reflog, merge/rebase/cherry-pick

  • git checkout vereint zwei unterschiedliche Aufgaben in einem Befehl
    • git checkout BRANCH wechselt den Branch
    • git checkout file.txt verwirft unstaged Änderungen an file.txt
  • Um diese Verwirrung zu verringern, hat Git die Funktionen in git switch und git restore aufgeteilt, aber das bestehende checkout kann weiterhin verwendet werden
  • reflog steht für reference log und ist das Protokoll darüber, worauf eine Referenz in der Vergangenheit gezeigt hat
    • reference ist ein Oberbegriff, der Branches, Tags und HEAD umfasst
    • reflog kann helfen, sich aus sehr schlechten Git-Situationen zu retten, etwa wenn ein wichtiger Branch versehentlich gelöscht wurde
  • merge, rebase und cherry-pick unterscheiden sich darin, wie sie Commits zusammenführen
    • merge erzeugt einen neuen Commit, der zwei Branches zusammenführt
    • rebase kopiert die Commits des aktuellen Branches nacheinander auf den Ziel-Branch
    • cherry-pick ist ähnlich wie rebase, aber die Syntax ist anders und der Commit wird auf den aktuellen Branch kopiert
  • git rebase --onto main otherbranch mybranch kann verwendet werden, um nur einen Teil der Commits von mybranch auf main zu verschieben
    • In einer Beispielstruktur könnte man damit nur F und G auf main rebasen
    • Die Syntax mit drei Branch-Namen ist schwer zu merken

commit und andere verwirrende Begriffe

  • commit wird in Git sowohl als Verb als auch als Substantiv verwendet
    • Verb: „committe häufig“
    • Substantiv: der neueste Commit auf main
  • Ein Git-Commit kann auf drei Arten betrachtet werden
    • als Snapshot des aktuellen Zustands aller Dateien
    • als diff zum Eltern-Commit
    • als History aller vorherigen Commits
  • Verschiedene Befehle behandeln einen Commit unterschiedlich
    • git show behandelt einen Commit wie einen diff
    • git log behandelt einen Commit wie History
    • git restore behandelt einen Commit wie einen Snapshot
  • Auch blob, tree, origin, upstream, downstream, die Beziehung zwischen fetch und pull, sowie stash, worktree, subtree und submodule gehören zu den verwirrenden Git-Begriffen
  • Unter den GitHub-Begriffen gelten „pull request“, „squash and merge“ und „rebase and merge“ als typische verwirrende Beispiele

Drei wiederkehrende Achsen der Verwirrung

  • Besonders verwirrende Git-Terminologie lässt sich auf drei Punkte verdichten
    • head ist ein Branch, HEAD ist der aktuelle Branch
    • „Remote-Tracking-Branch“ und „Branch, der einen Remote trackt“ sind verschiedene Dinge
    • index, staged und cached bezeichnen alle dasselbe Objekt
  • Der Text the most confusing git terminology aus dem Jahr 2012 behandelt auch, wie Git-Begriffe mit Begriffen aus CVS und Subversion zusammenhängen
  • Die Verwirrung in der Git-Terminologie taucht nicht nur bei einigen Spezialfunktionen auf, sondern wiederholt sich in den zentralen Funktionen insgesamt
  • Besonders beim Thema Branches wird „tracking“ auf mehrere Arten verwendet, sodass man die Unterschiede beim ersten Lesen leicht übersieht
  • Einige Erklärungen enthalten seltene Ecken von Git, daher bleibt der Hinweis, dass sich Fehler eingeschlichen haben könnten

1 Kommentare

 
GN⁺ 2023-11-03
Hacker-News-Kommentare
  • Ich konnte mir die meisten dieser Begriffe nur schwer merken, aber der entscheidende Gedanke, durch den alles zusammenpasste, war, dass vieles in Git letztlich Pointer auf Commits sind
    Branch-Namen, HEAD und Tags sind allesamt Pointer. HEAD zeigt auf den Commit, den man gerade betrachtet, und ein Branch-Name wie my-feature zeigt auf den neuesten Commit dieses Branches. Wenn man auf main git checkout -b my-feature ausführt, zeigen main, my-feature und HEAD alle auf den neuesten Commit von main
    Jedes Mal, wenn man auf my-branch committet, wandern HEAD und my-branch weiter und zeigen auf den neuen Commit. detached HEAD bedeutet, dass kein Branch auf den Commit zeigt, auf den HEAD zeigt. Der Unterschied zwischen Tags und Branches ist, dass Tags auf einen bestimmten Commit zeigen und sich nicht bewegen
    Ein weiterer Punkt, der mich verwirrt hat, war, dass viele Befehle deshalb inkonsistent wirken, weil Git Standardargumente annimmt. git checkout file.txt ist dasselbe wie git checkout HEAD -- file.txt, und wenn man auf my-branch ist, ist git rebase main dasselbe wie git rebase main my-branch. Die zweite Form kann man allerdings auch von einem anderen Branch aus ausführen
    Und schließlich ist der Befehl, der einen aus jedem seltsamen Zustand rettet, wenn alles verknotet ist, git reflog. Er zeigt alle Commits, auf die HEAD gezeigt hat. Trotzdem ist es schön zu sehen, dass Git diese Verwirrung anerkennt und Befehle mit weniger überraschenden und einfacheren Interfaces entwickelt
    • Wenn man den Namen verwendet, den Git selbst diesem Konzept gibt, dann sind „Commit-Pointer“ Refs. Daher kommt auch der Name reflog
      Was viele Git-Nutzer außerdem nicht auf dem Schirm haben: Auch die vom Remote geholten Branch- oder Tag-Refs sind ganz normale Refs und können überall referenziert werden, wo man einen Ref-Namen verwenden kann
      Wenn man zum Beispiel sagen will: „Egal, was in diesem Branch ist, kein Fast-Forward, kein Merge, kein Rebase, überschreibe einfach meinen lokalen Branch mit dem Inhalt des Remote“, dann geht das so: git checkout foo, git reset --hard origin/foo
    • Genau genommen zeigt der HEAD-Pointer nicht direkt auf einen Commit, sondern normalerweise auf einen Branch-Pointer wie my-branch, und dieser Branch-Pointer zeigt dann auf den Commit. Der detached-HEAD-Zustand ist die Ausnahme
      Wenn man auf my-branch committet, bewegen sich nicht HEAD und my-branch beide zum neuen Commit; HEAD zeigt weiterhin auf den Pointer my-branch, und nur my-branch wird weiterbewegt, sodass er auf den neuen Commit zeigt. Wenn man HEAD folgt und über my-branch weiter zum Commit geht, landet man natürlich beim neuen Commit
      Detached HEAD bedeutet, dass HEAD direkt auf den Commit zeigt statt auf einen Branch-Pointer. Auch in einem detached-HEAD-Zustand können HEAD und ein Branch-Pointer beide auf den neuesten Commit zeigen. In git log --decorate wird der neueste Commit dann statt des üblichen (HEAD -> my-branch) als (HEAD, my-branch) angezeigt
    • Diese Erklärung ist gut. Der gelegentlich noch verwirrendere Punkt ist allerdings, dass es zwei Arten von Tags gibt. Die meisten Tags sind, wie beschrieben, Lightweight-Tags, die auf ein Commit-Objekt zeigen, und mehr sind sie nicht
      Annotated Tags dagegen können eine Nachricht, einen Zeitstempel, SHA usw. enthalten. Das sind richtige Git-Objekte und sie verhalten sich Commit-Objekten ziemlich ähnlich, verweisen aber meist weiterhin auf ein anderes Git-Objekt, in der Regel einen Commit
    • Wenn man so übervorsichtig ist wie ich, ist es auch sinnvoll, vor einem heiklen Verfahren mit vielen Konflikten oder mehreren Sprüngen git rev-parse HEAD auszuführen und diesen String an einen sicheren Ort zu kopieren
      Das gibt einem zusätzlich die Sicherheit, den Zustand, von dem man sicher weiß, dass er in Ordnung war, exakt markiert zu haben. Man kann dann meist zum letzten bekannten guten Zustand zurückkehren statt zu irgendeinem Punkt, an dem man schon ein paar Schritte tief im Chaos war. Vorausgesetzt natürlich, man hat nichts wie git gc oder git filter-branch unvorsichtig benutzt
    • Diese Erklärung zeigt auch gut, dass die Baumstruktur im Inneren von Git alles bestimmt
      Es klingt wie ein Witz, ist aber keiner: Die Aussage, man brauche für Git wirklich einen Informatikabschluss, ist nicht ganz falsch. Ich stimme zu, dass Gits Standardargumente eine sehr inkonsistente User Experience erzeugen
      git checkout ., git reset --hard/soft und git cleanup sehen alle extrem ähnlich aus, sind in Wirklichkeit aber sehr verschieden. Trotzdem ist Git immer noch besser als Perforce oder SVN. Früher mochte ich Mercurial etwas lieber; ob das heute noch so wäre, weiß ich nicht
      git reflog ist der König und zeigt die Wahrheit
  • Es ist jetzt etwa zehn Jahre her, dass Git dank eines weiteren unbegründeten Technologie-Hypes die Versionsverwaltungskriege „gewonnen“ hat, und trotzdem hat es noch immer Schwierigkeiten bei Aufgaben, die banal sein sollten
    Ich meine mich zu erinnern, etwas von Linus Torvalds gelesen zu haben, in dem er überrascht war, dass Leute Git direkt benutzten, statt eine freundlichere Schicht darüberzulegen. Wenn die Branche zu Selbstreflexion fähig wäre, hätte sie eingeräumt, dass die Entscheidung für Git ein Fehler war, und wäre zu einem anderen Versionsverwaltungssystem gewechselt. Stattdessen verschwenden wir gewaltige Mengen an Zeit mit einem Werkzeug, das im Kern nur Text speichert
    • Ich sehe Git nicht als Werkzeug zum Speichern von Text. Es ist ein Werkzeug, das Änderungen, die mehrere Menschen an demselben „Text“ vornehmen, ziemlich präzise und zuverlässig koordiniert, ohne den Fortschritt irgendjemandes zu blockieren. Wenn man das einmal mit Word versucht, merkt man den Unterschied deutlich
      Natürlich kann es besser werden. Trotzdem überrascht mich der Hass, den Git abbekommt, immer wieder. Git ist ein großartiges Werkzeug und Word oder Google Docs, die für Nichtentwickler gemacht wurden, weit voraus
      Vielleicht liegt das auch daran, dass ich alt genug bin, mich an die Zeit zu erinnern, als Subversion König war
    • Stimme ich voll zu
      Jede Woche sehe ich Kollegen, die von Git verwirrt sind, ich sehe Hilfegesuche im Netz, und wieder einmal sehe ich einen Beitrag wie diesen hier, der versucht, etwas neu zu erklären, das einfach sein sollte
      Ich kann mich nicht daran erinnern, jemals Leute gesehen zu haben, die beim Coden stundenlang damit verschwendet haben, ihr eigenes Chaos in SVN, TFVC, Perforce usw. rückgängig zu machen
      Werkzeuge sollen das Leben einfacher machen. Wenn sie das nicht tun, sind sie die investierte Zeit nicht wert
    • Noch 2013 war überhaupt nicht klar, dass Git gewinnen würde. Es gab ziemlich viele Nutzer von darcs, Mercurial, svn und sogar cvs, und Werkzeuge aus dieser Zeit hatten normalerweise Plugins, die sie alle unterstützten

Ich wünschte, es gäbe noch immer diese Vielfalt. Die Git-Monokultur bedeutet auch, dass man, um Git zu ersetzen, zuerst Git neu implementieren muss. Deshalb werden wir daran fast für immer gebunden sein, wie an ASCII oder die Scroll-Lock-Taste.

  • Git hat aus guten Gründen gewonnen und war eindeutig besser als das, was davor da war. Heute ist es vielleicht wie bei jQuery in Mode, auf Git zu schimpfen, aber als es erschien, war es eine klare Verbesserung.
  • Ich habe kürzlich einen ähnlichen Artikel darüber geschrieben, warum wir möglicherweise in Git gefangen bleiben.
    Die zentrale Sorge ist, dass GitHub so gut ist, dass wir an Git gebunden bleiben, solange GitHub Git nicht von der Zeichenverwaltungs-Engine trennt und keine anderen Engines zulässt.
    https://trolololo.xyz/github
    https://news.ycombinator.com/item?id=38098109
  • Die Git-Terminologie ergibt bei vielen Begriffen zwar Sinn, wenn man erst einmal verstanden hat, wie alles funktioniert, aber ohne die Implementierungsdetails zu kennen oder wenn man sie neben andere Begriffe stellt, wirkt sie fast wie ein klinischer Fall.
    Begriffe wie leaky abstraction.
    • Das stimmt teilweise, aber auch deshalb, weil verschiedene Teile von Git von unterschiedlichen Leuten gebaut und separat benannt wurden. Linus nannte das ursprünglich cache, und das war der informatischste Begriff.
      Später hat Junio es anscheinend in das stärker auf verteilte Versionsverwaltungssysteme zugeschnittene index geändert, und die meisten Nutzer nannten es einfach staging area. Heute ist diese Entwicklung der Begriffe wie ein Fossil in Git-UI und Interna erhalten.
    • Es ergibt nicht wirklich Sinn, es ist einfach schlampige Benennung. Gute Namen zu vergeben erfordert Arbeit, und die frühen Git-Entwickler haben diese Arbeit nicht geleistet.
    • Wenn man sich vorstellt, man müsste für die Grundnutzung die Interna von Betriebssystem, IDE und Compiler kennen, dann ist das effektiv schlechtes API-Design.
  • „Your branch is up to date with origin/master“ ist die größte Lüge, die ich jeden Tag höre. Deshalb mache ich einfach jedes Mal pull.
    • Streng genommen ist es wahr. origin/master bedeutet nicht master auf origin, sondern den Commit, auf den das zuletzt bekannte origin/master gezeigt hat. Dieser Wert wird bei fetch aktualisiert, und pull macht automatisch ein fetch.
      Wenn du ls -l .git/refs/remotes/origin/master ansiehst, merkst du, dass origin/master nur eine Datei auf deinem System ist. Du kannst sogar sehen, wann sie geändert wurde. Sie wird nicht auf magische Weise aktualisiert.
      Wenn du git fetch origin ausführst und es Änderungen gibt, ändern sich Zeitstempel und Inhalt. Mit cat .git/refs/remotes/origin/master kannst du das prüfen.
      Die Grundlagen von Git sind sehr einfach, und die zentralen Datenstrukturen sowie einige Operationen könnte man in einem Tag implementieren. Das zu wissen ist wirklich wertvoll. Irgendwie hat Git es geschafft, auf einem ziemlich einfachen Kern eine sehr komplexe Benutzeroberfläche aufzubauen.
    • Die Meldung bedeutet nur, dass Git nicht ins Internet oder zum Remote-Repository geht, um nach Updates zu sehen, wenn man es nicht ausdrücklich dazu anweist. Diese Eigenschaft an sich halte ich für wünschenswert. Die Meldung könnte nur klarer sein.
    • Eigentlich hätte dort stehen sollen: „Your branch is up to date with the local copy of origin/master, last updated on XXXX-XX-XX:XX:XX:XX“
    • Ich lösche meinen lokalen master-Branch einfach. Normalerweise kann ich ohnehin nicht nach master pushen oder will dort sowieso nicht direkt arbeiten. Dann gibt es auch keinen Grund, einen lokal änderbaren master-Branch zu behalten.
      Also muss ich nur origin/master aktualisieren, und dafür reicht fetch.
    • git fetch && git pull
      Ich warte auf den Tag, an dem ich in drei Jahren in Rente gehe und mit diesem kaputten Müll endlich nichts mehr zu tun habe.
  • Ich benutze Git seit fast den ganz frühen Tagen, und erst diese Woche habe ich von porcelain erfahren. Ich habe ein Projekt, das die Ausgabe von git status parst, und der Schalter --porcelain hilft wirklich. Er erzeugt eine kompaktere Ausgabe, die sich leichter programmatisch parsen lässt.
    Dadurch wurde ich neugierig, bei wie vielen Befehlen es noch besser maschinenlesbare Ausgaben gibt, und landete schließlich auf der git-scm-Seite „Git Internals - Plumbing and Porcelain“. Kurz gesagt wurde Git anfangs eher als Werkzeugsammlung zum Umgang mit Versionsverwaltung geschrieben als als ausgefeiltes Versionsverwaltungssystem.
    Viele Leute, die Git früh benutzt haben, lernten Versionsverwaltungsaufgaben mit diesen Low-Level-Befehlen und gaben diesen Workflow an andere weiter. Das ist die plumbing-Schicht. Später bekam Git eine stärker polierte Schicht, und die nennt sich porcelain.
    Ich weiß noch nicht ganz, welche Befehle zu welcher Schicht gehören, aber es hat mir geholfen, die in den letzten Jahren empfohlenen neuen Workflows besser zu verstehen. Es hilft mir auch, fundierter zu entscheiden, wenn ich meinen eigenen Workflow ändere.
    https://git-scm.com/book/en/v2/Git-Internals-Plumbing-and-Po...
    • Das ist ein ziemlich klares Signal dafür, dass Linus mit der internen Struktur, dem plumbing, zufrieden war, aber fand, dass das UI, das porcelain, noch Arbeit brauchte. Ursprünglich war porcelain komplett aus Skripten aufgebaut, die plumbing aufriefen.
      Leider wurde dieses UI später fest einkompiliert, um es schneller zu machen, aber nicht verbessert.
  • Julias Schreibstimme ist wirklich großartig. Sie schreibt über Themen, die selbst für erfahrene Entwickler sehr wertvoll sind, und behält dabei einen freundlichen, nicht ausgrenzenden Ton für Leute bei, die gerade erst in dieses Feld einsteigen.
  • Ich glaube, ich habe „Git-Stockholm-Syndrom“. Diese Begriffe sind für mich gar nicht so verwirrend, aber das liegt wahrscheinlich daran, dass ich seit ungefähr 2011 tief in Git feststecke und fast keine anderen Systeme gelernt habe.
    Rückblickend war ich am Anfang sicher extrem verwirrt, aber ich glaube, ich habe so getan, als wäre das nicht so, um cool zu wirken.
    • Erst nachdem ich die CLI des Versionsverwaltungssystems Fossil gesehen hatte, wurde mir klar, dass ich Stockholm-Syndrom habe. Es war so intuitiv.
    • Vor Git habe ich cvs und svn benutzt, und im Vergleich zu svn war Git viel leichter zu verstehen.
    • Die einzige andere Versionsverwaltung, die ich benutzt habe, ist Plastic SCM, und ob Stockholm-Syndrom oder nicht: Git ist deutlich besser.
  • Ebenfalls sehenswert: Git staging area ist buchstäblich der Begriff, auf den sich alle geeinigt haben

https://news.ycombinator.com/item?id=28143078
„Your branch is up to date with ‘origin/main’“ ist tatsächlich etwas irreführend. Man kann es so verstehen, dass der Branch main aktuell ist, aber in Wirklichkeit ist das nicht so.
Allerdings muss man hier nicht zu sehr auf „streng genommen“ pochen. Gilt hier nicht das Zwei-Generäle-Problem? Wenn man es wirklich penibel betrachtet, kann man nicht wissen, ob es genau in diesem Moment „aktuell“ ist. Auch wenn man vor status ein fetch macht, gilt dasselbe.
Was wäre dann eine vernünftige Erwartung? Etwa, dass der Ref in der Objektdatenbank auf meinem Computer mit dem Ref in einer weit entfernten anderen Objektdatenbank übereinstimmt, und zwar so, wie ich es zuletzt überprüft habe.
Ein einfacher Aufruf von status kann nicht jedes Mal ein Netzwerk-fetch durchführen oder die Grundprinzipien des gerade verwendeten Werkzeugs in Erinnerung rufen. Ich persönlich sehe das so. Überraschenderweise meinen jedoch viele Kommentare unter der StackOverflow-Antwort, dass genau diese beiden Dinge nötig seien.
Eine Formulierung wie „is up to date with the origin’s main as of your last fetch 5 days ago“ wäre etwas vernünftiger. Sie teilt nur mit, wie lange der letzte fetch her ist.
[1] https://stackoverflow.com/questions/27828404/why-does-git-st...
Noch etwas: Ich dachte, ORIG_HEAD hätte wie FETCH_HEAD mit dem „Head von origin“ zu tun. Aber das ist nicht so. Dieser „pseudoref“ scheint als Sicherungspunkt vor komplexeren Umschreibungen wie Rebase verwendet zu werden und wurde implementiert, bevor es Reflogs gab. Vermutlich bedeutet es „original head“.

  • Ich bin Git gegenüber ziemlich wohlwollend eingestellt, aber bei der Formulierung dieser Meldung stimme ich dem Autor zu. Mit der vorgeschlagenen Alternative bin ich allerdings nicht einverstanden.
    „Your branch is up to date with ‘origin/main’“ ist technisch korrekt, aber der Ausdruck up to date impliziert beim flüchtigen Lesen ungefähr zwei Dinge: dass main gleich origin/main ist und dass auch origin/main aktuell ist, also während des Befehls aktualisiert wurde oder automatisch auf dem neuesten Stand gehalten wird und die letzte Synchronisierung erfolgreich war.
    Das ist kein maschinenlesbares Signal, das für genau einen CPU-Zyklus wahr sein muss, sondern eine für Nutzer sichtbare Statusmeldung. Auch ohne Netzwerktheorie ist diese Interpretation vernünftig.
    „Up to date“ bedeutet eher, zeitlich aufgeholt zu haben, nicht räumlich. Die Position eines lokalen Branches ist eher räumlich im Sinn von „Auf welchen Punkt zeigt dieser Branch?“, während der Zustand eines Remote-Refs eher zeitlich ist im Sinn von „Wann habe ich diesen Remote-Ref zuletzt aktualisiert?“. Natürlich ist das sehr subjektiv, und beides kann in beide Richtungen verstanden werden.
    Eine bessere Formulierung könnte etwas sein wie „Your branch matches origin/main“, „Your branch’s head is the same as origin/main“ oder „Your branch is pointing to the same commit as origin/main“. Das wäre ein Kompromiss zwischen Klarheit und Ausführlichkeit. Dazu könnte man wie vom Autor vorgeschlagen in Klammern „remote ref last updated 5 days ago“ anhängen.
  • Ich stimme nicht zu, dass es ärgerlich wäre, wenn ein status-Aufruf ein Netzwerk-fetch machen würde. Meiner Meinung nach sollten Netzwerkoperationen explizit und nicht implizit sein. Wenn ich git status ausführe, möchte ich den Zustand meines Repositories im Dateisystem kennen.
    Wenn ich wissen will, was sich auf einem anderen Remote tut, kann ich selbst fetch ausführen und dann vergleichen.
  • Ich erwarte, Informationen zu erhalten, die zum Zeitpunkt des Starts des status-Befehls aktuell sind. Zwischen dem Ermitteln der Informationen und ihrer Ausgabe im Terminal kann es Race Conditions geben, aber das ist nur ein sehr kleines Zeitfenster.
    Im Großen und Ganzen bedeutet das, dass man fetch will. Das Zwei-Generäle-Problem gilt nur für die Remote-Seite. Das Remote kann bei Bedarf eine Weile weiter erneut senden, wenn es keine Bestätigung erhält, dass der Client die Daten bekommen hat. Das ist nicht das Problem des Clients.
    Wenn der Client die Daten nicht vom Server erhalten kann, wäre das korrekte Verhalten wohl, stehen zu bleiben und nach einem Timeout einen Fehler auszugeben; vermutlich macht fetch das bereits so.
  • Der wertvollste Schritt beim Lernen von Git ist, das richtige mentale Modell zu haben. Es ist alles ein Graph.
    Dieses Tool wird oft geteilt und passt auch hier sehr gut. Es visualisiert das interne Modell von Git und die Auswirkungen verschiedener Befehle.
    https://learngitbranching.js.org/
    Ich habe in 10 Minuten mit diesem Tool mehr gelernt als in ungefähr den 10 Jahren Git-Erfahrung davor. Sehr empfehlenswert.
  • Ich will eigentlich nicht noch mehr drauflegen, aber dass die Bitbucket-Leute den Begriff pull request geprägt haben, ist auch ein Problem.
    „Pull“ ist die Aktion, Remote-Änderungen in ein lokales Repository zu übernehmen. Was der Nutzer tatsächlich anfragt, ist, dass der Server seine Remote-Änderungen in einen bestimmten Branch zusammenführt. GitLabs „merge request“ ist die treffendere Bezeichnung.
    Ich habe einmal gesehen, wie jemand nach einem fetch dachte, Commits seien verschwunden. Tatsächlich waren sie nur verborgen, weil das Repository zeitlich zurückgesprungen war. Git versteckt solche Details in status; wenn man nicht git-prompt oder powerline-shell nutzt, arbeitet man praktisch im Dunkeln.
    • Die ursprüngliche Idee, die in git-request-pull(1) weiterlebt, war, dass wir alle unsere eigenen Git-Repositories auf Orten wie kernel.org/git oder redhat.com/git hätten und uns per E-Mail darum bitten würden, aus Repositories auf unterschiedlichen Servern, in unterschiedlichen Organisationen und Domains zu pullen.
      GitHub ließ sich vom Git-Befehl request-pull inspirieren, interpretierte ihn aber als Zusammenführen von einem GitHub-Repository in ein anderes GitHub-Repository um.
    • Aus Sicht des Servers ist der Nutzer remote und der Server lokal. Deshalb bittet der Nutzer den Server, von seiner Seite zu pullen.
      Zumindest habe ich es immer so verstanden.
    • Das Git-Buch erklärt die Herkunft dieses Begriffs und warum er Sinn ergibt.
      Kurz gesagt ist es keine Anfrage an den Server, sondern an eine andere Person. Man bittet die andere Person, den eigenen Branch zu pullen, damit sie sich die Änderungen ansehen kann, die man zum Projekt beitragen möchte.

[0] https://git-scm.com/book/en/v2/Distributed-Git-Contributing-...

  • „Pull Request“ wurde von GitHub popularisiert, und ich halte es für einen brauchbaren Begriff. Die Unterscheidung, ob tatsächlich aus einem anderen Repository gepullt wird oder innerhalb desselben Repositorys ein „Merge Request“ erfolgt, ist nicht besonders interessant. „Merge Request“ wirkt wie ein GitLab-spezifischer Begriff.
  • Das war für mich immer am schwersten zu verstehen. Eigentlich denkt man doch, es müsste selbstverständlich kein Pull Request, sondern eher ein Push Request oder ein Merge Request sein.