Wie kann ich mehrere Git-Commits zurücksetzen?

983

Ich habe ein Git-Repository, das so aussieht:

A -> B -> C -> D -> HEAD

Ich möchte, dass der Kopf des Zweigs auf A zeigt, dh ich möchte, dass B, C, D und HEAD verschwinden, und ich möchte, dass der Kopf gleichbedeutend mit A ist.

Es hört sich so an, als könnte ich entweder versuchen, die Basis neu zu definieren (gilt nicht, da ich Änderungen dazwischen verschoben habe) oder zurücksetzen. Aber wie kann ich mehrere Commits zurücksetzen? Werde ich nacheinander zurücksetzen? Ist die Reihenfolge wichtig?

Rechnung
quelle
3
Wenn Sie die Fernbedienung nur zurücksetzen möchten, können Sie sie mit allem überhäufen! Aber lassen Sie uns das vierte Commit vor verwenden: git push -f HEAD~4:master(vorausgesetzt, der Remote-Zweig ist Master). Ja, Sie können jedes Commit so pushen.
u0b34a0f6ae
21
Wenn Leute gezogen haben, müssen Sie ein Commit durchführen, das Änderungen mit zurücksetzt git revert.
Jakub Narębski
1
Verwenden Sie git show HEAD ~ 4, um sicherzustellen, dass Sie nach rechts auf die Fernbedienung
drücken
5
"Ist die Reihenfolge wichtig?" Ja, wenn die Commits dieselben Zeilen in denselben Dateien betreffen. Dann sollten Sie das letzte Commit zurücksetzen und sich zurückarbeiten.
Avandeursen

Antworten:

1336

Erweitern, was ich in einem Kommentar geschrieben habe

Die allgemeine Regel lautet, dass Sie den von Ihnen veröffentlichten Verlauf nicht neu schreiben (ändern) sollten, da möglicherweise jemand seine Arbeit darauf basiert hat. Wenn Sie den Verlauf neu schreiben (ändern), treten Probleme beim Zusammenführen der Änderungen und beim Aktualisieren auf.

Die Lösung besteht also darin, ein neues Commit zu erstellen , das Änderungen zurücksetzt , die Sie entfernen möchten. Sie können dies mit dem Befehl git revert tun .

Sie haben folgende Situation:

A <- B <- C <- D <- Master <- HEAD

(Die Pfeile beziehen sich hier auf die Richtung des Zeigers: die "übergeordnete" Referenz bei Commits, die oberste Commit bei Verzweigungskopf (Verzweigungsreferenz) und der Name der Verzweigung bei HEAD-Referenz).

Was Sie erstellen müssen, ist Folgendes:

A <- B <- C <- D <- [(BCD) ^ - 1] <- Master <- HEAD

wobei "[(BCD) ^ - 1]" das Commit bedeutet, das Änderungen in den Commits B, C, D zurücksetzt. Die Mathematik sagt uns, dass (BCD) ^ - 1 = D ^ -1 C ^ -1 B ^ -1, also Sie können die erforderliche Situation mit den folgenden Befehlen erhalten:

$ git revert --no-commit D
$ git revert --no-commit C
$ git revert --no-commit B
$ git commit -m "the commit message"

Mögliche Lösung wäre, Kasse Inhalt der commit A, und verpflichten diesen Zustand:

$ git checkout -f A -- .
$ git commit -a

Dann hätten Sie folgende Situation:

A <- B <- C <- D <- A '<- master <- HEAD

Das Festschreiben A 'hat den gleichen Inhalt wie Festschreiben A, ist jedoch ein anderes Festschreiben (Festschreibungsnachricht, Eltern, Festschreibungsdatum).

Die von Charles Bailey modifizierte Lösung von Jeff Ferland baut auf derselben Idee auf, verwendet jedoch Git-Reset :

$ git reset --hard A
$ git reset --soft @{1}  # (or ORIG_HEAD), which is D
$ git commit
Jakub Narębski
quelle
40
Wenn Sie Dateien in B, C oder D hinzugefügt git checkout -f A -- .haben, werden diese nicht manuell gelöscht. Sie müssen dies manuell tun. Ich habe diese Strategie jetzt angewendet, danke Jakub
oma
18
Diese Lösungen sind nicht gleichwertig. Der erste löscht keine neu erstellten Dateien.
m33lky
10
@ Jerry: Kann git checkout fooCheckout- Zweig foo (Wechsel zum Zweig) oder Checkout- Datei foo (vom Index) bedeuten . --wird verwendet, um zu disambiguieren, zB git checkout -- foogeht es immer um Datei.
Jakub Narębski
87
Neben toller Antwort. Diese Kurzschrift funktioniert für michgit revert --no-commit D C B
welldan97
9
@ welldan97: Danke für einen Kommentar. Beim Schreiben dieser Antwort git revertwurden nicht mehrere Commits akzeptiert. es ist ziemlich neu hinzugekommen.
Jakub Narębski
248

Sauberer Weg, den ich nützlich fand

git revert --no-commit HEAD~3..

Dieser Befehl setzt die letzten 3 Commits mit nur einem Commit zurück.

Schreibt auch die Geschichte nicht um.

Tieftauchgang
quelle
16
Dies ist die einfache und beste Antwort
Julien Deniau
3
@ JohnLittle es inszeniert die Änderungen. git commitvon dort wird eigentlich das Commit machen.
x1a4
14
Dies funktioniert nicht, wenn einige Commits Merge-Commits sind.
MegaManX
5
Was machen die beiden Punkte am Ende?
Kardamom
5
@cardamom Diese geben einen Bereich an. HEAD~3..ist das gleiche wieHEAD~3..HEAD
Toine H
238

Zu tun , so dass Sie nur die verwenden müssen revert Befehl unter Angabe des Bereichs von Commits wollen Sie kehrten erhalten.

Unter Berücksichtigung Ihres Beispiels müssten Sie dies tun (vorausgesetzt, Sie befinden sich in der Zweigstelle 'Master'):

git revert master~3..master

Dadurch wird in Ihrem lokalen Bereich ein neues Commit mit dem inversen Commit von B, C und D erstellt (was bedeutet, dass die durch diese Commits eingeführten Änderungen rückgängig gemacht werden):

A <- B <- C <- D <- BCD' <- HEAD
Sieger
quelle
129
git revert --no-commit HEAD~2..ist eine etwas idiomatischere Art, dies zu tun. Wenn Sie sich im Hauptzweig befinden, müssen Sie den Master nicht erneut angeben. Mit dieser --no-commitOption kann git versuchen, alle Commits auf einmal zurückzusetzen, anstatt den Verlauf mit mehreren revert commit ...Nachrichten zu verunreinigen (vorausgesetzt, Sie möchten dies).
Kubi
6
@ Victor Ich habe Ihren Commit-Bereich festgelegt. Der Anfang des Sortiments ist exklusiv, dh es ist nicht enthalten. Wenn Sie also die letzten 3 Commits zurücksetzen möchten, müssen Sie den Bereich vom übergeordneten Element des 3. Commits aus starten , d master~3. H.
2
@kubi gibt es keine Möglichkeit, die SHAs mit einem einzigen Commit in eine Commit-Nachricht aufzunehmen (Ihre Methode, ohne jedoch die zurückgesetzten Commits manuell eingeben zu müssen)?
Chris S
@ChrisS Mein erster Gedanke wäre, nicht zu verwenden --no-commit(Sie erhalten also für jedes Zurücksetzen ein separates Commit) und dann alle zusammen in einer interaktiven Rebase zu quetschen. Die kombinierte Festschreibungsnachricht enthält alle SHAs, und Sie können sie mit Ihrem bevorzugten Festschreibungsnachrichten-Editor nach Belieben anordnen.
Radon Rosborough
71

Ähnlich wie bei Jakubs Antwort können Sie auf einfache Weise aufeinanderfolgende Commits zum Zurücksetzen auswählen.

# revert all commits from B to HEAD, inclusively
$ git revert --no-commit B..HEAD  
$ git commit -m 'message'
konyak
quelle
9
Ihre Lösung hat für mich gut funktioniert, aber mit einer geringfügigen Änderung. Wenn wir diesen Fall Z -> A -> B -> C -> D -> HEAD haben und wenn ich in den A-Zustand zurückkehren möchte, dann müsste ich seltsamerweise git revert --no-commit Z ausführen. KOPF
Bogdan
3
Stimmen Sie mit @Bogdan überein, der Wiederherstellungsbereich ist wie folgt: SHA_TO_REVERT_TO..HEAD
Vadym Tyemirov
11
Der Bereich ist falsch. Es sollte sein B^..HEAD, sonst ist B ausgeschlossen.
Tessus
3
Stimmen Sie mit @tessus überein, also wäre das Richtige: git revert --no-commit B^..HEADodergit revert --no-commit A..HEAD
Yoho
64
git reset --hard a
git reset --mixed d
git commit

Das wird für alle gleichzeitig eine Rückkehr bedeuten. Geben Sie eine gute Commit-Nachricht.

Jeff Ferland
quelle
4
Wenn er HEADso aussehen möchte , möchte Aer wahrscheinlich, dass der Index übereinstimmt, git reset --soft Dwas wahrscheinlich angemessener ist.
CB Bailey
2
--soft resetting verschiebt den Index nicht. Wenn er also festschreibt, sieht es so aus, als ob das Festschreiben direkt von a statt von D erfolgt. Dadurch würde der Zweig aufgeteilt. --mixed überlässt die Änderungen, verschiebt jedoch den Indexzeiger, sodass D zum übergeordneten Commit wird.
Jeff Ferland
5
Ja, ich denke, Git Reset - Keep ist genau das, was ich oben habe. Es erschien in Version 1.7.1, die im April 2010 veröffentlicht wurde, daher gab es zu diesem Zeitpunkt keine Antwort.
Jeff Ferland
git checkout Adann git commitoben hat bei mir nicht funktioniert, aber diese Antwort hat funktioniert.
SimplGy
Warum ist git reset --mixed Derforderlich? Speziell warum reset? Liegt es daran, dass HEAD ohne Zurücksetzen auf D auf A zeigt und B, C und D "baumelt" und Müll sammelt - was er nicht will? Aber warum dann --mixed? Sie haben bereits geantwortet: " --softZurücksetzen verschiebt den Index nicht ..." Wenn Sie also den Index verschieben, bedeutet dies, dass der Index die Änderungen von D enthält, während das Arbeitsverzeichnis die Änderungen von A enthält - auf diese Weise ein git statusoder git diff(das Index [D] mit vergleicht Arbeitsverzeichnis [A]) zeigt den Stoff; Dieser Benutzer wechselt von D zurück zu A?
Die rote Erbse
39

Stellen Sie zunächst sicher, dass Ihre Arbeitskopie nicht geändert wird. Dann:

git diff HEAD commit_sha_you_want_to_revert_to | git apply

und dann einfach festschreiben. Vergessen Sie nicht zu dokumentieren, was der Grund für die Rücknahme ist.

mateusz.fiolka
quelle
1
Hat für mich gearbeitet! Es gab ein Problem mit dem Veralten von Änderungen im Feature-Zweig, die im Entwicklungszweig vorgenommen wurden (einige Fehlerkorrekturen), sodass der Feature-Zweig alle in der Entwicklung vorgenommenen Änderungen überschreiben musste (einschließlich des Löschens einiger Dateien).
Silentser
1
Funktioniert nicht mit Binärdateien:error: cannot apply binary patch to 'some/image.png' without full index line error: some/image.png: patch does not apply
GabLeRoux
2
Dies ist eine viel flexiblere Lösung als die akzeptierte Antwort. Vielen Dank!
Brian Kung
2
re: Binärdateien verwenden --binary Option: git diff --binary HEAD commit_sha_you_want_to_revert_to | git apply
weinerk
2
Dies funktioniert auch dann, wenn Sie einen Bereich von Commits zurücksetzen möchten, der Merge-Commits enthält. Wenn git revert A..ZSie verwenden, erhalten Sieerror: commit X is a merge but no -m option was given.
Juliusz Gonera
35

Ich bin so frustriert, dass diese Frage nicht einfach beantwortet werden kann. Jede andere Frage bezieht sich darauf, wie man richtig zurückkehrt und die Geschichte bewahrt. Diese Frage lautet: "Ich möchte, dass der Kopf des Zweigs auf A zeigt, dh ich möchte, dass B, C, D und HEAD verschwinden und dass der Kopf gleichbedeutend mit A ist."

git checkout <branch_name>
git reset --hard <commit Hash for A>
git push -f

Ich habe viel gelernt, als ich Jakubs Post gelesen habe, aber ein Typ in der Firma (mit Zugriff auf unsere "Test" -Zweige ohne Pull-Request) hat wie 5 schlechte Commits versucht, einen Fehler zu beheben, den er vor 5 Commits gemacht hat. Nicht nur das, sondern auch ein oder zwei Pull-Anfragen wurden akzeptiert, die jetzt schlecht waren. Also vergiss es, ich habe das letzte gute Commit gefunden (abc1234) und nur das Basisskript ausgeführt:

git checkout testing
git reset --hard abc1234
git push -f

Ich sagte den anderen 5 Jungs, die in diesem Repo arbeiten, dass sie ihre Änderungen in den letzten Stunden und Wipe / Re-Branch von den letzten Tests besser notieren sollten. Das Ende der Geschichte.

Suamere
quelle
2
Ich hatte die Commits nicht veröffentlicht, daher war dies die Antwort, die ich brauchte. Danke, @Suamere.
Tom Barron
Der bessere Weg, dies zu tun, besteht darin git push --force-with-lease, die Geschichte nur dann neu zu schreiben, wenn sich nach oder innerhalb des Bereichs der zu verdampfenden Commits niemand anderes für den Zweig verpflichtet hat. Wenn andere Personen den Zweig verwendet haben, sollte sein Verlauf niemals neu geschrieben werden, und das Festschreiben sollte einfach sichtbar rückgängig gemacht werden.
frandroid
1
@frandroid "Geschichte sollte niemals umgeschrieben werden", nur der Sith-Deal in absoluten Zahlen. Die Frage dieses Threads und der Punkt meiner Antwort ist genau, dass für ein bestimmtes Szenario der gesamte Verlauf gelöscht werden sollte.
Suamere
@Suamere Klar, das ist die Frage. Aber da Ihre Antwort erwähnt, was Sie den anderen Jungs zu sagen hatten, besteht die Möglichkeit von Problemen. Aus persönlicher Erfahrung kann push -f Ihre Codebasis durcheinander bringen, wenn andere Personen nach dem, was Sie löschen möchten, eine Verpflichtung eingegangen sind. --force-with-lease erreicht das gleiche Ergebnis, außer dass es dir den Arsch rettet, wenn du dein Repo durcheinander bringen willst. Warum die Chance nutzen? Wenn --force-with-lease fehlschlägt, können Sie sehen, welches Commit im Weg steht, richtig bewerten, anpassen und es erneut versuchen.
frandroid
1
@ Suamere Danke! Ich stimme zu, dass die Frage eindeutig besagt, dass die Geschichte neu geschrieben werden soll. Ich bin in der gleichen Situation wie Sie und ich vermute, dass jemand Dutzende hässlicher Rückschläge und seltsame Commits und Rückschläge versehentlich (während ich im Urlaub war) gemacht hat und der Staat zurückgesetzt werden muss. In jedem Fall sollte dies zusammen mit einer guten gesunden Warnung die akzeptierte Antwort sein.
Lee Richardson
9

Dies ist eine Erweiterung einer der in Jakubs Antwort enthaltenen Lösungen

Ich war mit einer Situation konfrontiert, in der die Commits, die ich zurücksetzen musste, etwas komplex waren. Einige der Commits waren Merge-Commits, und ich musste vermeiden, den Verlauf neu zu schreiben. Ich konnte eine Reihe von git revertBefehlen nicht verwenden, da ich schließlich auf Konflikte zwischen den hinzugefügten Umkehrungsänderungen stieß. Am Ende habe ich die folgenden Schritte ausgeführt.

Überprüfen Sie zunächst den Inhalt des Ziel-Commits, während Sie HEAD an der Spitze des Zweigs belassen:

$ git checkout -f <target-commit> -- .

(Das - stellt sicher, dass <target-commit>es als Commit und nicht als Datei interpretiert wird. Das. Verweist auf das aktuelle Verzeichnis.)

Stellen Sie dann fest, welche Dateien in den zurückgesetzten Commits hinzugefügt wurden und daher gelöscht werden müssen:

$ git diff --name-status --cached <target-commit>

Dateien, die hinzugefügt wurden, sollten am Anfang der Zeile mit einem "A" angezeigt werden, und es sollten keine weiteren Unterschiede bestehen. Wenn nun Dateien entfernt werden müssen, stellen Sie diese Dateien zum Entfernen bereit:

$ git rm <filespec>[ <filespec> ...]

Zum Schluss die Umkehrung festschreiben:

$ git commit -m 'revert to <target-commit>'

Stellen Sie auf Wunsch sicher, dass wir wieder im gewünschten Zustand sind:

$git diff <target-commit> <current-commit>

Es sollte keine Unterschiede geben.

Warren Dew
quelle
Sind Sie sicher, dass Sie gitnur mit der Spitze des Zweiges KOPFEN können ?
Suamere
2
Dies war eine viel bessere Lösung für mich, da ich in meinen Zusammenführungsverpflichtungen hatte.
Sovemp
3

Die einfache Möglichkeit, eine Gruppe von Commits im freigegebenen Repository (die von Benutzern verwendet wird und Sie den Verlauf beibehalten möchten) zurückzusetzen, besteht in der Verwendung git revertin Verbindung mit git rev-list. Der letztere liefert Ihnen eine Liste der Commits, der erstere führt das Zurücksetzen selbst durch.

Dafür gibt es zwei Möglichkeiten. Wenn Sie mehrere Commits in einem einzigen Commit zurücksetzen möchten, verwenden Sie:

for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert -n $i; done

Dadurch wird eine Gruppe von Commits zurückgesetzt, die Sie benötigen. Wenn Sie jedoch alle Änderungen in Ihrem Arbeitsbaum belassen, sollten Sie sie wie gewohnt festschreiben.

Eine andere Option besteht darin, ein einziges Commit pro rückgängig gemachter Änderung durchzuführen:

for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert --no-edit -s $i; done

Zum Beispiel, wenn Sie einen Commit-Baum wie haben

 o---o---o---o---o---o--->    
fff eee ddd ccc bbb aaa

Um die Änderungen von eee auf bbb zurückzusetzen , führen Sie aus

for i in `git rev-list eee^..bbb`; do git revert --no-edit -s $i; done
Ruslan Kabalin
quelle
habe das gerade benutzt. Vielen Dank!
Ran Biron
2

Keiner von diesen hat für mich funktioniert, also musste ich drei Commits zurücksetzen (die letzten drei Commits), also tat ich:

git revert HEAD
git revert HEAD~2
git revert HEAD~4
git rebase -i HEAD~3 # pick, squash, squash

Lief wie am Schnürchen :)

Dorian
quelle
2
Dies ist nur dann eine praktikable Option, wenn Ihre Änderungen noch nicht übernommen wurden.
Kboom
0

Meiner Meinung nach könnte ein sehr einfacher und sauberer Weg sein:

gehe zurück zu A.

git checkout -f A

Zeigen Sie mit dem Kopf des Masters auf den aktuellen Status

git symbolic-ref HEAD refs/heads/master

speichern

git commit
nulll
quelle
1
Können Sie bitte den Grund für die Ablehnung erklären?
nulll
Das funktioniert gut. Was bringt es, diese nützliche Antwort abzulehnen, oder kann jemand erklären, was die beste Vorgehensweise ist?
Levent Divilioglu
Ist das dasselbe wie git checkout master; git reset --hard A? Oder wenn nicht, könnten Sie etwas mehr darüber erklären, was dies bewirkt?
MM
funktioniert einfach, aber symbolisch-ref HEAD scheint kein "sicherer" Befehl zu sein
Sérgio
äh, ich will keinen Fix Master, ich will einen Zweig reparieren
Sérgio
-6

Wenn Sie die Commits eines Features vorübergehend zurücksetzen möchten, können Sie die folgenden Befehle verwenden.

So funktioniert es

git log --pretty = oneline | grep 'feature_name' | cut -d '' -f1 | xargs -n1 git revert --no-edit

Ajit Singh
quelle