Wie entfernst du eine bestimmte Revision im Git-Verlauf?

213

Angenommen, Ihre Git-Geschichte sieht folgendermaßen aus:

1 2 3 4 5

1–5 sind separate Revisionen. Sie müssen 3 entfernen, während Sie 1, 2, 4 und 5 behalten. Wie geht das?

Gibt es eine effiziente Methode, wenn nach der zu löschenden Version Hunderte von Revisionen vorliegen?

1800 INFORMATIONEN
quelle
Diese Frage ist schlecht definiert. Es heißt nicht ausdrücklich, dass der Autor 1-2- (3 + 4) -5 oder 1-2-4-5
RandyTek
14
Nun, 8 Jahre später kann ich Ihnen nicht genau sagen, welches Problem ich lösen wollte. Aber in Git gibt es immer viele Möglichkeiten, etwas zu tun, und es gibt viele Antworten, die verschiedenen Menschen gefallen haben. Ich denke, die Mehrdeutigkeit bereitet nicht zu vielen Menschen große Schwierigkeiten
1800 INFORMATION
1
git rebase --onto 2 3 HEADbedeutet so ziemlich Rebase auf 2, mit Commits zwischen 3 und HEAD (HEAD ist optional oder 5 in diesem Fall)
neaumusic

Antworten:

76

Um Revision 3 und 4 zu einer einzigen Revision zu kombinieren, können Sie Git Rebase verwenden. Wenn Sie die Änderungen in Revision 3 entfernen möchten, müssen Sie den Befehl edit im interaktiven Rebase-Modus verwenden. Wenn Sie die Änderungen in einer einzigen Revision kombinieren möchten, verwenden Sie Squash.

Ich habe diese Squash-Technik erfolgreich angewendet, musste aber noch nie eine Revision entfernen. Die git-rebase-Dokumentation unter "Splitting Commits" sollte Ihnen hoffentlich genug Ideen geben, um es herauszufinden. (Oder jemand anderes könnte es wissen).

Aus der Git-Dokumentation :

Beginnen Sie mit dem ältesten Commit, das Sie unverändert beibehalten möchten:

git rebase -i <after-this-commit>

Ein Editor wird mit allen Commits in Ihrem aktuellen Zweig gestartet (Merge-Commits werden ignoriert), die nach dem angegebenen Commit erfolgen. Sie können die Commits in dieser Liste nach Herzenslust neu anordnen und entfernen. Die Liste sieht mehr oder weniger so aus:

pick deadbee Die Oneline dieses Commits
Wählen Sie fa1afe1. Die Online des nächsten Commits
...

Die Online-Beschreibungen dienen ausschließlich Ihrem Vergnügen. git-rebase betrachtet sie nicht, sondern die Commit-Namen (in diesem Beispiel "deadbee" und "fa1afe1"). Löschen oder bearbeiten Sie die Namen also nicht.

Indem Sie den Befehl "pick" durch den Befehl "edit" ersetzen, können Sie git-rebase anweisen, nach dem Anwenden dieses Commits zu stoppen, damit Sie die Dateien und / oder die Commit-Nachricht bearbeiten, das Commit ändern und das Rebasing fortsetzen können.

Wenn Sie zwei oder mehr Commits zu einem falten möchten, ersetzen Sie den Befehl "pick" durch "squash" für das zweite und nachfolgende Commit. Wenn die Commits unterschiedliche Autoren hatten, wird das gequetschte Commit dem Autor des ersten Commits zugewiesen.

garethm
quelle
42
-1 Die Frage ist gut definiert, aber diese Antwort ist nicht so klar. Der Autor sagt nicht, was die genaue Lösung ist.
Aleksandr Levchuk
1
Dies ist eine falsche Spur. Der Abschnitt SPLITTING COMMITS ist nicht der richtige. Sie möchten viel höher im Handbuch lesen - siehe die Antwort von @Rares Vernica.
Aleksandr Levchuk
1
@AleksandrLevchuk Die Frage ist nicht genau definiert: Es ist unklar, wie die Frage angegeben wird, ob der Änderungssatz in 3 beibehalten oder verworfen werden soll. Ich werde zustimmen, dass die anderen Antworten einen einfacheren Ansatz bieten, wenn die Änderungen verworfen werden sollen. Wenn die Änderungen jedoch beibehalten werden sollen, wäre dies eine rein kosmetische Operation. Beide Ansätze schreiben die Geschichte auf eine Weise neu, die gefährlich ist, wenn andere möglicherweise auf der fehlerhaften Geschichte basieren. Wenn dies der Fall ist, sollte die kosmetische Bereinigung nicht durchgeführt werden, und das Löschen der Änderung sollte besser über git revert erfolgen.
Theodore Murdock
124

Gemäß diesem Kommentar (und ich habe überprüft, ob dies wahr ist) ist Rados Antwort sehr nah, lässt Git jedoch in einem Zustand mit losgelöstem Kopf zurück. Entfernen Sie stattdessen Folgendes HEAD, um es <commit-id>aus dem Zweig zu entfernen, in dem Sie sich befinden:

git rebase --onto <commit-id>^ <commit-id>
kareem
quelle
1
Wenn Sie mir sagen könnten, wie ich diese Commit-ID auch aus der Historie entfernen kann, die nur auf der Commit-ID basiert, wären Sie mein Held.
KayleeFrye_onDeck
Können Sie bitte erklären, was der magische Befehl in der Antwort bewirkt? Dh was jeder Parameter bezeichnet?
Alexandroid
Das ist großartig, aber was ist, wenn ich das allererste Commit in der Geschichte entfernen möchte? (weshalb ich hierher gekommen bin: P)
Sterling Camden
2
Aus irgendeinem Grund passiert nichts, wenn ich das ausführe. Das Ändern ^, ~1damit es funktioniert.
Antimon
Viel zu spät, aber ich werde hinzufügen, dass Sie, wenn Sie auf Zusammenführungskonflikte stoßen, die Rebase abbrechen und sie --strategy-option theirsam Ende erneut ausführen müssen .
LastStar007
122

Hier ist eine Möglichkeit, eine bestimmte nicht interaktiv zu entfernen <commit-id>, wobei Sie nur die wissen, die <commit-id>Sie entfernen möchten:

git rebase --onto <commit-id>^ <commit-id> HEAD
Rado
quelle
2
Hat auch für mich gearbeitet. Übrigens, was macht der Operator ^? Bedeutet dies das nächste Commit nach dem angegebenen?
Hopia
3
@hopia bedeutet das (erste) übergeordnete Element des angegebenen Commits. Siehe "Git Help Revisionen"
Emil Styrke
12
Siehe @ kareems Empfehlung in seiner Antwort, wegzulassen HEAD, um einen abgetrennten Kopf zu vermeiden.
mklement0
1
Viel einfacher, als herauszufinden, wie viele Commits für die Suche zurückgegeben werden.
Dana Woodman
1
Das ist großartig, aber was ist, wenn ich das allererste Commit in der Geschichte entfernen möchte? (weshalb ich hierher gekommen bin: P)
Sterling Camden
76

Wie bereits erwähnt, ist git-rebase (1) dein Freund. Angenommen, die Commits befinden sich in Ihrer masterNiederlassung, würden Sie Folgendes tun:

git rebase --onto master~3 master~2 master

Vor:

1---2---3---4---5  master

Nach dem:

1---2---4'---5' master

Aus Git-Rebase (1):

Eine Reihe von Commits könnte auch mit Rebase entfernt werden. Wenn wir die folgende Situation haben:

E---F---G---H---I---J  topicA

dann der Befehl

git rebase --onto topicA~5 topicA~3 topicA

würde zur Entfernung der Commits F und G führen:

E---H'---I'---J'  topicA

Dies ist nützlich, wenn F und G in irgendeiner Weise fehlerhaft waren oder nicht Teil von Thema A sein sollten. Beachten Sie, dass das Argument für --onto und der Parameter ein beliebiges gültiges Commit-ish sein kann.

Rvernica
quelle
3
sollte nicht das --onto master~3 master~1?
Matthias
3
Wenn Sie einfach das letzte Commit löschen möchten, ist es --onto master ~ 1 master
MikeHoss
Ich möchte dieses Sol tragen. aber ich bekomme den MERGE CONFLICTFehler. Ich habe das unter stackoverflow.com/questions/2938301/remove-specific-commit erwähnte Szenario verwendet und kann das zweite Commit in diesem Beispiel nicht entfernen.
maan81
22

Wenn Sie nur die in Revision 3 vorgenommenen Änderungen entfernen möchten, können Sie git revert verwenden.

Git Revert erstellt einfach eine neue Revision mit Änderungen, die alle Änderungen in der Revision, die Sie zurücksetzen, rückgängig machen.

Dies bedeutet, dass Sie Informationen sowohl über das unerwünschte Festschreiben als auch über das Festschreiben behalten, mit dem diese Änderungen entfernt werden.

Dies ist wahrscheinlich viel freundlicher, wenn es überhaupt möglich ist, dass jemand in der Zwischenzeit aus Ihrem Repository gezogen hat, da das Zurücksetzen im Grunde nur ein Standard-Commit ist.

SpoonMeiser
quelle
4
Leider keine gute Lösung für mich, da jemand versehentlich 100 MB Mist in das Repo gesteckt hat, die Größe in die Luft gesprengt und das Webinterface träge gemacht hat.
Stephen Smith
18

Alle bisherigen Antworten sprechen nicht das nachfolgende Problem an:

Gibt es eine effiziente Methode, wenn nach der zu löschenden Version Hunderte von Revisionen vorliegen?

Die Schritte folgen, aber als Referenz nehmen wir den folgenden Verlauf an:

[master] -> [hundreds-of-commits-including-merges] -> [C] -> [R] -> [B]

C : Festschreiben direkt nach dem zu entfernenden Festschreiben (sauber)

R : Das zu entfernende Commit

B : Festschreiben unmittelbar vor dem zu entfernenden Festschreiben (Basis)

Aufgrund der Einschränkung "Hunderte von Revisionen" gehe ich von folgenden Voraussetzungen aus:

  1. Es gibt ein peinliches Commit, von dem Sie sich wünschen, es gäbe es nie
  2. Es gibt NULL nachfolgende Commits, die tatsächlich von diesem peinlichen Commit abhängen (keine Konflikte beim Zurücksetzen).
  3. Es ist Ihnen egal, dass Sie als "Committer" der Hunderte von intervenierenden Commits aufgeführt werden ("Autor" bleibt erhalten).
  4. Sie haben das Repository noch nie freigegeben
    • oder Sie haben tatsächlich genug Einfluss auf alle Menschen, die jemals Geschichte mit diesem Engagement geklont haben, um sie davon zu überzeugen, Ihre neue Geschichte zu verwenden
    • und Sie kümmern sich nicht um die Geschichte umzuschreiben

Dies ist eine ziemlich restriktive Reihe von Einschränkungen, aber es gibt eine interessante Antwort, die in diesem Eckfall tatsächlich funktioniert.

Hier sind die Schritte:

  1. git branch base B
  2. git branch remove-me R
  3. git branch save
  4. git rebase --preserve-merges --onto base remove-me

Wenn es wirklich keine Konflikte gibt, sollte dies ohne weitere Unterbrechungen erfolgen. Wenn es Konflikte gibt, können Sie diese lösen und / rebase --continueoder sich entscheiden, nur mit der Verlegenheit und zu leben rebase --abort.

Jetzt sollten Sie darauf sein, masterdass kein Commit R mehr darin enthalten ist . Der saveZweig zeigt auf Ihre vorherige Position, falls Sie eine Abstimmung durchführen möchten.

Wie Sie die Übertragung aller anderen auf Ihre neue Historie arrangieren möchten, liegt bei Ihnen. Sie müssen mit vertraut sein stash, reset --hardund cherry-pick. Und Sie können die löschen base, remove-meund saveZweige

jdsumsion
quelle
Wie kann ich das 150000 mal mögen?
Gena Moroz
Ich habe für mich gearbeitet, um 3 Commits nacheinander aus der Mitte des Verlaufs eines Zweigs zu entfernen, in dem jemand eine Reihe von 150-MB-Dateien festgeschrieben hat.
Marcos
3

Ich bin auch in einer ähnlichen Situation gelandet. Verwenden Sie die interaktive Rebase mit dem folgenden Befehl und lassen Sie bei Auswahl das dritte Commit fallen.

git rebase -i remote/branch
Sankalp
quelle
2

Hier ist das Szenario, mit dem ich konfrontiert war, und wie ich es gelöst habe.

[branch-a]

[Hundreds of commits] -> [R] -> [I]

Hier Rist das Commit, das ich entfernen musste, und es Iist ein einzelnes Commit, das danach kommtR

Ich habe ein Revert Commit gemacht und sie zusammengedrückt

git revert [commit id of R]
git rebase -i HEAD~3

Während des interaktiven Rebase-Squashs werden die letzten 2 Commits ausgeführt.

Gautam
quelle
0

Die Antworten von Rado und Kareem tun nichts für mich (nur die Meldung "Aktueller Zweig ist aktuell." Wird angezeigt). Möglicherweise geschieht dies, weil das Symbol '^' in der Windows-Konsole nicht funktioniert. Nach diesem Kommentar löst das Ersetzen von '^' durch '~ 1' das Problem.

git rebase --onto <commit-id>^ <commit-id>
fdermishin
quelle