Wie kann ich ein in der Geschichte vergrabenes Git-Commit aufteilen?

292

Ich habe meine Geschichte durcheinander gebracht und möchte einige Änderungen daran vornehmen. Das Problem ist, dass ich ein Commit mit zwei nicht zusammenhängenden Änderungen habe und dieses Commit von einigen anderen Änderungen in meinem lokalen (nicht gepushen) Verlauf umgeben ist.

Ich möchte dieses Commit aufteilen, bevor ich es herausbringe, aber die meisten Anleitungen, die ich sehe, haben mit dem Aufteilen Ihres letzten Commits oder nicht festgeschriebenen lokalen Änderungen zu tun. Ist es möglich, dies für ein Commit zu tun, das ein wenig in der Geschichte vergraben ist, ohne meine Commits seitdem erneut ausführen zu müssen?

Ben
quelle

Antworten:

450

In der Rebase-Manpage finden Sie eine Anleitung zum Aufteilen von Commits . Die kurze Zusammenfassung lautet:

  • Führen Sie eine interaktive Rebase mit dem Ziel-Commit (z. B. git rebase -i <commit-to-split>^ branch) durch und markieren Sie es als zu bearbeitend.

  • Wenn die Rebase dieses Commit erreicht, verwenden Sie diese Option, um git reset HEAD^sie vor dem Commit zurückzusetzen. Behalten Sie jedoch Ihren Arbeitsbaum bei.

  • Fügen Sie schrittweise Änderungen hinzu und schreiben Sie sie fest, wobei Sie so viele Festschreibungen wie gewünscht vornehmen. add -pkann nützlich sein, um nur einige der Änderungen in einer bestimmten Datei hinzuzufügen. Verwenden commit -c ORIG_HEADSie diese Option, wenn Sie die ursprüngliche Festschreibungsnachricht für eine bestimmte Festschreibung erneut verwenden möchten.

  • Wenn Sie testen möchten, was Sie festschreiben (gute Idee!) git stash, Um den Teil zu verbergen, den Sie nicht festgeschrieben haben (oder stash --keep-indexbevor Sie ihn überhaupt festschreiben), testen Sie und git stash popgeben Sie den Rest an den Arbeitsbaum zurück. Machen Sie so lange Commits, bis Sie alle Änderungen festgeschrieben haben, dh einen sauberen Arbeitsbaum haben.

  • Führen Sie git rebase --continuediese Option aus, um die Commits nach dem jetzt aufgeteilten Commit anzuwenden.

Cascabel
quelle
17
... aber nichts davon, wenn Sie den Verlauf bereits seit dem Commit zum Teilen verschoben haben.
Wilhelmtell
29
@wilhelmtell: Ich habe mein übliches "potenziell gefährlich; siehe" Wiederherstellung von Upstream-Rebase "weggelassen", weil das OP ausdrücklich sagte, er habe diese Geschichte nicht vorangetrieben.
Cascabel
2
und du hast perfekt gelesen. Ich habe versucht, das "Boilerplate" zu vermeiden, als ich angab, dass es noch kein gemeinsamer Verlauf ist :) In jeder Hinsicht hatte ich Erfolg mit Ihrem Vorschlag. Es ist allerdings ein großer Schmerz, dieses Zeug nachträglich zu machen. Ich habe hier eine Lektion gelernt, und das soll sicherstellen, dass die Commits zunächst korrekt eingegeben werden!
Ben
2
Der erste Schritt kann besser ausgedrückt werden als git rebase -i <sha1_of_the_commit_to_split>^ branch. Und git guiist ein nettes Tool für die Aufteilungsaufgabe, mit dem verschiedene Teile einer Datei zu verschiedenen Commits hinzugefügt werden können.
Qiang Xu
3
@ QiangXu: Der erste ist ein vernünftiger Vorschlag. Das zweite ist genau der Grund, warum ich vorgeschlagen habe git add -p, was mehr git guikann als in dieser Abteilung (insbesondere das Bearbeiten von Hunks, das Staging von allem ab dem aktuellen Hunk und das Suchen nach Hunks durch Regex).
Cascabel
3

So geht's mit Magit .

Angenommen, Commit ed417ae ist dasjenige, das Sie ändern möchten. Es enthält zwei nicht zusammenhängende Änderungen und ist unter einem oder mehreren Commits begraben. Hit lldas Protokoll zu zeigen, und navigieren Sie zu ed417ae:

erstes Protokoll

rDrücken Sie dann , um das Rebase-Popup zu öffnen

Popup neu starten

und mum das Commit am Punkt zu ändern.

Beachten Sie, wie sich das @Commit jetzt aufteilt, das Sie teilen möchten. Dies bedeutet, dass sich HEAD jetzt bei diesem Commit befindet:

Festschreiben eines Commits

Wir möchten HEAD auf das übergeordnete Element verschieben. Navigieren Sie also zum übergeordneten Element (47e18b3), drücken Sie x( magit-reset-quicklygebunden an, owenn Sie es verwenden evil-magit) und geben Sie "Ja, ich meinte Festschreiben an Punkt" ein. Ihr Protokoll sollte nun folgendermaßen aussehen:

Protokoll nach dem Zurücksetzen

Nun traf qzum regulären Magit Status zu gehen, verwenden Sie dann den regulären unstage uBefehl unstage was nicht in geht das erste zu begehen, zu begehen , cden Rest wie üblich, dann stage und commit was geht das zweite begehen, und wenn Sie fertig sind : treffen rdie rebase Popup zu öffnen

Popup neu starten

und noch eine, rum fortzufahren, und du bist fertig! llzeigt jetzt:

alles erledigt log

Unhammer
quelle
1

Um ein Commit aufzuteilen <commit>und das neue Commit vor diesem hinzuzufügen und das Autorendatum von zu speichern, gehen Sie <commit>wie folgt vor:

  1. Bearbeiten Sie das Commit zuvor <commit>

    git rebase -i <commit>^^
    

    NB: Vielleicht muss es auch bearbeitet <commit>werden.

  2. Kirsche <commit>in den Index holen

    git cherry-pick -n <commit>
    
  3. Setzen Sie nicht benötigte Änderungen aus dem Index interaktiv zurück und setzen Sie den Arbeitsbaum zurück

    git reset -p && git checkout-index -f -a
    

    Als Alternative können Sie nicht benötigte Änderungen interaktiv aufbewahren: git stash push -p -m "tmp other changes"

  4. Nehmen Sie weitere Änderungen vor (falls vorhanden) und erstellen Sie das neue Commit

    git commit -m "upd something" .
    

    Wiederholen Sie optional die Punkte 2 bis 4, um weitere Zwischen-Commits hinzuzufügen.

  5. Setzen Sie die Neubasierung fort

    git rebase --continue
    
Ruvim
quelle
0

Es gibt eine schnellere Version, wenn Sie nur Inhalte aus nur einer Datei extrahieren möchten. Es ist schneller, weil die interaktive Rebase nicht mehr interaktiv ist (und es ist natürlich noch schneller, wenn Sie aus dem letzten Commit extrahieren möchten, dann müssen Sie überhaupt keine Rebase mehr erstellen).

  1. Verwenden Sie Ihren Editor und löschen Sie die Zeilen, aus denen Sie extrahieren möchten the_file. Schließen the_file. Dies ist die einzige Edition, die Sie benötigen. Der Rest sind nur Git-Befehle.
  2. Stellen Sie diese Löschung im Index bereit:

    git  add  the_file
    
  3. Stellen Sie die gerade gelöschten Zeilen wieder in der Datei wieder her, ohne den Index zu beeinflussen !

    git show HEAD:./the_file > the_file
    
  4. "SHA1" ist das Commit, aus dem Sie die Zeilen extrahieren möchten:

    git commit -m 'fixup! SHA1' 
    
  5. Erstellen Sie das zweite brandneue Commit mit dem zu extrahierenden Inhalt, der in Schritt 3 wiederhergestellt wurde:

    git commit -m 'second and new commit' the_file 
    
  6. Nicht bearbeiten, nicht anhalten / fortfahren - einfach alles akzeptieren:

    git rebase --autosquash -i SHA1~1
    

Natürlich noch schneller, wenn das Commit, aus dem extrahiert werden soll, das letzte Commit ist:

4. git commit -C HEAD --amend
5. git commit -m 'second and new commit' thefile
6. no rebase, nothing

Wenn Sie verwenden, sind magitSchritt 4, 5 und 6 eine einzelne Aktion: Festschreiben, sofortige Korrektur

März
quelle
-2

Wenn Sie noch nicht gedrückt haben, verwenden Sie einfach git rebase. Noch besser ist git rebase -ies, Commits interaktiv zu verschieben. Sie können das fehlerhafte Commit nach vorne verschieben, es dann nach Belieben aufteilen und die Patches nach hinten verschieben (falls erforderlich).

Gintautas Miliauskas
quelle
14
Es ist nicht nötig, es irgendwohin zu bewegen. Teilen Sie es auf, wo es ist.
Cascabel
1
Leider funktioniert dies bei mir nicht, da ein Teil des Verlaufs nach dem Festschreiben davon abhängt, sodass ich ein wenig eingeschränkt bin. Dies wäre jedoch meine erste Wahl gewesen.
Ben
@Ben: das ist okay - die Commits danach müssen sich überhaupt nicht ändern (vorausgesetzt, Sie behalten alle Änderungen bei, anstatt einige davon wegzuwerfen). Weitere Informationen hier - stackoverflow.com/questions/1440050/…
Ether