Der Befehl `git history`
(lalitm.com)- Der experimentelle Befehl
git history, der in Git 2.54 und 2.55 eingeführt wurde, automatisiert das Bearbeiten früherer Commits und das Neuaufbauen abgeleiteter Branches, ohne den bestehenden Git-Workflow zu verändern fixupfügt gestagte Änderungen in einen früheren Commit ein und rebased anschließend automatisch alle lokalen Branches, die von diesem Commit abgeleitet sind, auf die neuen Commit-Hashesrewordändert die Nachricht eines früheren Commits, undsplitteilt die Änderungen eines Commits auf Hunk-Ebene in zwei Commits auf und baut danach nachfolgende Commits und Branches neu auf- Die drei Unterbefehle verhindern mit atomarer Verarbeitung, dass der Working Tree in einem kaputten Zwischenzustand zurückbleibt, indem sie Operationen mit möglichem Konflikt ablehnen, funktionieren jedoch nicht bei Historien mit Merge-Commits
- Zwar bietet der Befehl weder die Konfliktbehandlung als erstklassiges Konzept, das rückgängig machen über ein Operations-Log noch die Modellierung der Working Copy als Commits wie
jj, aber mehrerejj-artige Funktionen zur Bearbeitung der Historie lassen sich direkt mit der Git-Core-Distribution ohne separate Installation nutzen
Warum die Bearbeitung der bestehenden Git-Historie unbequem ist
- Wenn mehrere Änderungen parallel bearbeitet werden, muss man ständig zwischen Branches und Commits wechseln, und während eines
rebase -ikann der Working Tree in einen unvollständigen Zustand geraten - Die Git-Alternative
jjwird oft als Lösung für dieses Problem genannt, doch selbst nach anderthalb Jahren mit Versuchen von jeweils einigen Tagen alle drei Monate kehrt man am Ende wieder zum bisherigen Git-Workflow zurück git historyist ein experimenteller Befehl, der in der Git-Core-Distribution enthalten ist, sodass man ihn ohne Installation eines zusätzlichen Tools ausprobieren kann- Die Unterbefehle bestehen aus den drei Varianten
fixup,rewordundsplit
fixup: Früheren Commit und abgeleitete Branches gleichzeitig korrigieren
git history fixupkorrigiert fehlerhafte Teile eines früheren Commits und rebased automatisch die Branches, die von diesem Commit abgeleitet wurden- Wie im üblichen Korrekturablauf werden Änderungen zunächst mit
git addgestagt; führt man danachgit history fixup <commit>aus, werden die gestagten Änderungen in den Ziel-Commit eingefügt - Das ähnelt einer Kombination aus
git commit --fixupund einem Autosquash-Rebase, mit dem Unterschied, dass auch andere Branches, die den Ziel-Commit enthalten, aktualisiert werden - Während
git rebase --update-refsnur Referenzen verschiebt, die im aktuellen Rebase-Bereich liegen, findetgit historyalle lokalen Branches, die vom Ziel-Commit abgeleitet sind, und schreibt sie neu- Falls nötig, lässt sich der Aktualisierungsbereich auf den aktuellen Branch beschränken
- In Historien mit Merge-Commits funktioniert der Befehl nicht, weshalb er sich nicht für jede Git-Nutzungsweise eignet
- Wenn Korrekturen in
Beingefügt werden, entstehtB*mit neuem Hash; auch die darüberliegendenCundDwerden alsC*bzw.D*neu erzeugt- Die Branch-Spitzen von
feat-1undfeat-2werden ebenfalls auf die neuen Commits verschoben
- Die Branch-Spitzen von
Atomare Verarbeitung und Konfliktbegrenzung
- Alle drei Unterbefehle werden atomar verarbeitet und hinterlassen den Working Tree nicht in einem halb kaputten Zustand
- Die Atomarität wird dadurch gewährleistet, dass Operationen, die Konflikte auslösen könnten, von vornherein abgelehnt werden
- Das ist funktional eingeschränkter als
jj, das Konflikte als erstklassige Objekte behandelt, einen Konfliktzustand während eines Rebase beibehalten und später auflösen kann - Die Einschränkungen von
git historysind bewusst so gesetzt, damit das Umschreiben der Historie nicht zu einer zustandserhaltenden Operation wird- Falls Git künftig erstklassige Konflikte unterstützt, könnten diese Einschränkungen aufgehoben werden
reword: Commit-Nachricht ohne Working Tree ändern
git history rewordändert die Nachricht eines früheren Commits und baut die darüberliegenden Commits und Branches automatisch neu auf- Führt man
git history reword <commit>aus, öffnet sich im Editor die bisherige Nachricht; nach dem Bearbeiten und Speichern wird der nachfolgende Commit-Stack an die neuen Hashes angepasst neu aufgebaut - Das ist nützlich, wenn sich während iterativer Entwicklung das Design geändert hat und ältere Commit-Nachrichten an die aktuelle Struktur angepasst werden sollen
- Abgesehen davon, dass statt des Tree-Inhalts nur die Commit-Nachricht geändert wird, funktioniert der Befehl wie
fixup rewordverändert weder Index noch Working Tree, sondern nur den Commit-Graphen- Auch Commits auf nicht ausgecheckten Branches lassen sich ändern, ohne die aktuelle Arbeit zu stören
- Wird die Nachricht von
Bgeändert, ändert sich dessen Hash; der nachfolgendeCwird daher alsC*neu erzeugt, und auchfeat-1folgt dem neuen Commit
split: Einen Commit in zwei aufteilen
git history splitteilt die Änderungen eines Commits anhand einer interaktiven Auswahl in zwei Commits auf- Führt man
git history split <commit>aus, öffnet sich eine Eingabeaufforderung, in der die Änderungen des Ziel-Commits auf Hunk-Ebene ausgewählt werden- Die ausgewählten und beibehaltenen Hunks bilden den ersten Commit
- Die übrigen Hunks kommen in den zweiten Commit
- Das ähnelt
git add -p, erfordert aber kein komplexes Verfahren mit einer Kombination ausgit rebase - Wird
B, das zwei nicht zusammenhängende Änderungen enthielt, aufgeteilt, entstehenB1undB2, und der nachfolgendeCwird darüber alsC*neu aufgebaut - Unter den drei Befehlen ist dies der spezialisierteste Anwendungsfall, aber wenn ein Commit tatsächlich aufgeteilt werden muss, ist er nützlich
Funktionsunterschiede zu jj
git historyersetzt nicht das vollständige Arbeitsmodell vonjjgit historyunterstützt dagegen innerhalb von bestehendem Git das Korrigieren früherer Commits, das Ändern von Commit-Nachrichten, das Aufteilen von Commits und das Aktualisieren abgeleiteter Branches, sodass sich einige Vorteile vonjjin den aktuellen Workflow übernehmen lassen- Da eine spätere Lockerung der Einschränkungen möglich bleibt, gibt es Raum für weitere Verbesserungen in kommenden Git-Releases
1 Kommentare
Meinungen auf Hacker News
Git war für mich unbequem, aber nachdem ich die ersten drei Kapitel des kostenlos verfügbaren Buchs Pro Git gelesen hatte, wurde es besser. Es vermittelt ein hervorragendes mentales Modell der internen Funktionsweise von Git, und ob gut oder schlecht: Die Benutzeroberfläche von Git spiegelt seine interne Struktur direkt wider. Sobald ich das verstanden hatte, ergab alles Sinn.
git checkoutzum Beispiel verliert beim Wechseln von Branches keine Änderungen, die noch nicht in Git gelandet sind, ist aber beim Wiederherstellen einer anderen Version einer Datei darauf ausgelegt, Änderungen zurückzusetzen. Inwiefern spiegelt das die interne Struktur direkt wider?Unabhängig davon erschwert auch die Inkonsistenz der CLI die Nutzung von Git: Selbst wenn man weiß, was man intern tun möchte und welchen Befehl man braucht, ist es schwer, die genaue Kommandozeilen-Syntax zu finden.
git commit -am “Changes”nicht aus.Es gibt
git rebase --abort, und man kann vor dem Rebase auch ein Tag erstellen und mitgit reset --hard $set_tagdorthin zurückkehren. Der vorherige Zustand verschwindet nicht, daher gibt es keinen Grund, Angst vor einem interaktiven Rebase zu haben.tempoderbefore-rebasean. Ich nutze sie wie Tags, aber Branches lassen sich bequemer verschieben, daher führe ich vor einem großenrebase -inormalerweisegit branch before-rebase/some-featureaus.Tatsächlich musste ich
git reset before-rebasekaum je verwenden, aber ich habe häufiggit log -p before-rebasemit dem Branch nach dem Rebase verglichen, um unbeabsichtigte Änderungen beim Auflösen von Konflikten zu prüfen.git reflog?jj undogeht es nicht.git rebaseeingegeben habe, gebe ich ungefähr in der Hälfte der Fälle direkt--abortein.Ich sehe mich selbst nicht als Coder oder Programmierer, aber Git zu lernen hat meinem Denken gefühlt eine Superkraft der Organisation gegeben. Ich nutze es für unzählige Zwecke: Websites, Designprojekte, Elektronik, Komposition, persönliche Wissensspeicher, Skripte zur Fernverwaltung, Konfigurationsverwaltung, Code-Schnipsel und mehr. Es ist nicht perfekt, aber ich empfehle allen, mit denen ich arbeite, es zu lernen.
In David Allens Getting Things Done geht es darum, dass man ein vertrauenswürdiges System braucht, um Gedanken zu sammeln und zu organisieren. Für Aufgaben, Projekte und Listen nutze ich OmniFocus, aber beim Organisieren und Bewahren von Software und einigen Daten, die ich am Computer erstellt habe, vertraue ich Git.
Ich mag diese Befehle theoretisch sehr und nutze auch die entsprechenden Befehle in
jjständig, konnte aber nicht herausfinden, wiegit historydie geänderten Commits signieren kann. Nachdem ich das Handbuch und den Git-Quellcode angesehen hatte, verschwinden alle Signaturen, wenn man einen bestehenden GPG-signierten Commit mitgit history reword HEAD~ändert.Weil ich die Commit-Signaturen beibehalten wollte, bin ich am Ende zum traditionellen
git rebase -izurückgekehrt.git historyals experimentelle Funktion bezeichnet, wäre es wohl einen Bugreport wert.Auch ohne neuen Befehl habe ich seit über zehn Jahren die lokale Git-Commit-Historie ohne größere Schwierigkeiten massiv umgeschrieben. Ich bin überzeugt, dass Git und Rebase vor allem wegen eines User-Interface-Problems schwierig wirken: Man muss die exakten Kommandozeilenoptionen und deren Verhalten selbst herausfinden.
Unter Windows war der Rebase-Dialog von TortoiseGit mächtig und zugleich leicht zu bedienen. Man klickt im Commit-Log mit der rechten Maustaste auf den Basis-Commit und wählt
"Rebase onto this"; wie unter https://tortoisegit.org/docs/tortoisegit/tgit-dug-rebase.htm... sind das Umordnen, Aufteilen, Zusammenführen und Bearbeiten von Commits klar, und wenn man zwischendurch abbricht, kehrt alles in den ursprünglichen Zustand zurück.Da es allerdings als Shell-Erweiterung umgesetzt ist, gibt es zu viele Klicks; als eigenständige App mit Tabs wäre es besser. Das scheint aber ein Designprinzip zu sein, das von TortoiseSVN übernommen wurde. Git Extensions habe ich auch kurz ausprobiert, konnte aber häufig genutzte Funktionen nicht finden oder war bereits zu sehr an TortoiseGit gewöhnt, um dabei zu bleiben. Die Log-Ansicht sieht man unter https://tortoisegit.org/docs/tortoisegit/tgit-dug-showlog.ht....
My Computerhinzufügen konnte. Meinem Verständnis nach erreichte das mit XP seinen Höhepunkt, und ab Vista änderte Microsoft es in eine Richtung, die Erweiterungen erschwerte.Allerdings verstehe ich bis heute nicht, warum irgendein Menü in VC++6 statt einer normalen Liste ein wie ein Ordner aussehendes Shell-Objekt erzeugte, das im Datei-öffnen-Dialog angezeigt wurde. Es war ein Assistent zum Hinzufügen von Komponenten zu einem Projekt, und während der Dialog geöffnet war, suchte man diesen Fake-Ordner unter
My Computer.fixupging bereits mitgit commit --fixupoder davor mitgit rebase -i, undrewordwar ebenfalls mitgit rebase -imöglich.Offenbar hat sich genug Feedback angesammelt, dass das Bearbeiten einer Datei, um für jeden Commit die auszuführende Aktion festzulegen, unbequem ist, sodass ein höherstufiger Befehl ergänzt wurde, der die bestehende Funktionalität kapselt.
Bei Kommandozeilentools ist nach dem nächsten Befehl der Zugriff auf die vorherige Ausgabe erschwert. Wenn man einen in
git loggesehenen Commit cherry-picken will, kann man den Hash nicht aus dem Gedächtnis eingeben, wenn er nicht mehr im Terminal steht; ein zustandsbehaftetes Interface kann dagegen mehrere Interaktionen parallel halten und frühere Informationen bewahren.Git hat sehr viele Zustandsinformationen, die man sich merken muss: den aktuellen Branch, den vorherigen Branch, geänderte Dateien, nicht gepushte Commits und mehr. Wenn man die umgebenden Werkzeuge wie Branch-Anzeige und Autovervollständigung in Shell-Plugins, mehrere Tabs und Fenster-Splits, Kopieren zwischen Tabs und Suche in der Autovervollständigung nicht beherrscht, kann die Git-Nutzung sehr unangenehm werden.
Die eigentliche Schwäche von
jjsehe ich nur in der Git-Integration. Es formalisiert domänenspezifische Sprachen für File Sets, Revision Sets und Templates, nutzt sie konsistent in Konfiguration und CLI und stellt mächtige Konzepte als Interface bereit, ohne die interne Struktur direkt offenzulegen.Es ist aber so flexibel, dass die Git-Interoperabilität stark von Konventionen und Disziplin abhängt, und weil sich fast jeder
jj-Zustand in Git ausdrücken lässt, kann man Remote-Repositories leicht beschädigen. Um das zu reparieren, muss man Git sehr gut verstehen.Für ein neues Einzelentwicklungsprojekt ist
jjeinen Versuch wert. Unter günstigen Bedingungen fühlt es sich an wie frühes Git im Vergleich zu Subversion; wenn man sich zuerst an die Eigenheiten vonjjgewöhnt, ist man später auch etwas besser auf die schwierige Git-Integration vorbereitet. Kontext dazu gibt es unter https://news.ycombinator.com/item?id=48903054.jjauch Nicht-Git-Backends, aber als derzeit produktionsgeeignet ist ausdrücklich nur das Git-Backend gekennzeichnet.jjohne Git-Integration ist noch eher Konzept als Realität.Ich mag
jjund sein mentales Modell von Versionskontrolle und werde es weiter verwenden, aber weil ich gelegentlich auf Git hinuntersteigen muss, um Arbeit abzuschließen, fühlt es sich eher wie ein komfortabler Wrapper auf Git an als wie ein neues Versionsverwaltungssystem. Wenn Git Befehle ergänzt, die denjj-Workflow unterstützen, wird es schwer, noch einen Grund zu finden,jjseparat zu installieren.Die Art, wie
git history fixupalle Nachfolger-Branches automatisch rebased, passt nicht zu meinem Workflow. Ich bewahre jede Version eines Feature-Branches alsmyfeature.1,myfeature.2usw. auf, um bei verändertem Verhalten in einer neuen Version eine alte Version wiederzufinden;git reflogkann das nicht ersetzen.Wenn ein Problem zum Beispiel zwischen
myfeature.58undmyfeature.59entstanden ist und jedes Feature 15 Commits hat, kann man mitgit range-diff myfeature.58~15..myfeature.58 myfeature.59~15..myfeature.59die Unterschiede der beiden Versionen commitweise ansehen, selbst wenn die Basis-Commits unterschiedlich sind. Daher möchte ich keinen automatischen Rebase aller Branches, die einen bestimmten Commit enthalten, und auch der Ansatz, alte Versionen mit Tags zu markieren, passte für mich nicht gut.Aktuelles Git hat drei häufig genutzte
git rebase --interactive-Aufgaben als separate Befehle mit weniger Reibung umgesetzt; sie scheinen nur zu funktionieren, wenn es keine Konflikte gibt.git historywirklich nützlich ist. Ich bevorzuge es, Commits per interaktivem Rebase visuell zu verschieben, und möchte nicht versehentlich den Hash eines verwaisten Commits, der aus dem aktuellen lokalen Branch verschwunden ist, als Ziel fürgit historyangeben.Ich verstehe nicht, warum man sich so viel Mühe gibt, die Git-Historie perfekt aufzuräumen. Einzelne Commits liest man am Ende sowieso nicht noch einmal, also kann man vor dem Merge einfach alles squashen
"oops"und"fix"ist, lehne ich ihn sofort abCommits erzählen die Geschichte der Arbeit, daher sollte man eine gut lesbare Commit-Historie liefern, damit Reviewer sich nicht zusätzlich abmühen müssen, die Absicht zu verstehen
Wenn Codebasis und Funktionen sehr klein sind, kann man alles squashen, aber ich bevorzuge eine Vorgehensweise, bei der ich den tatsächlichen Änderungsverlauf sehen und Commits gezielt cherry-picken oder zurücksetzen kann
Ich hatte verstanden: Wenn aktuell
Dausgecheckt ist und manBinB'ändert, lässtgit rebase --update-refsdas außerhalb des Rebase-Bereichs liegendeEunverändert, erzeugt nurB' → C' → D'und behält das bestehendeB → EbeiAktualisiert
git history fixupdagegen auchEzuE'? Falls ja, frage ich mich, ob es mit dem seit Jahrzehnten in Fleisch und Blut übergegangenengit rebaseebenfalls eine Möglichkeit gibt, alle Nachkommen-Branches zu aktualisierenEnicht unverändert bleiben. Ich habe--update-refsnicht selbst ausprobiert, aber der Dokumentation nach dürfte als Ergebnis der dritte Graph entstehen, also einschließlichE'; das gewünschte Verhalten ist damit bereits mitgit rebasemöglich