Wenn ich in der Vergangenheit ein Commit habe, das auf ein Elternteil verweist, aber das Elternteil ändern möchte, auf das es verweist, wie würde ich das tun?
Verwenden von git rebase
. Es ist der generische Befehl "take commit (s) und plopiere es auf ein anderes übergeordnetes Element (Basis)" in Git.
Einige Dinge zu wissen:
Da Commit-SHAs ihre Eltern einbeziehen, ändert sich beim Ändern des übergeordneten Elements eines bestimmten Commits dessen SHA - ebenso wie die SHAs aller Commits, die danach (neuer als sie) in der Entwicklungslinie folgen .
Wenn Sie mit anderen Personen zusammenarbeiten und das betreffende Commit bereits öffentlich an die Stelle verschoben haben, an der sie es abgerufen haben, ist das Ändern des Commits wahrscheinlich eine schlechte Idee. Dies liegt an Nummer 1 und damit an der daraus resultierenden Verwirrung, auf die die Repositorys der anderen Benutzer stoßen, wenn sie herausfinden möchten, was passiert ist, weil Ihre SHAs nicht mehr mit ihren für die "gleichen" Commits übereinstimmen. (Weitere Informationen finden Sie im Abschnitt "WIEDERHERSTELLUNG VON UPSTREAM REBASE" auf der verlinkten Manpage.)
Das heißt, wenn Sie sich derzeit in einem Zweig mit einigen Commits befinden, die Sie zu einem neuen Elternteil verschieben möchten, sieht dies ungefähr so aus:
git rebase --onto <new-parent> <old-parent>
Dadurch wird alles nach <old-parent>
dem aktuellen Zweig verschoben , um <new-parent>
stattdessen darauf zu sitzen .
--onto
es verwendet wird! Die Dokumentation war für mich auch nach dem Lesen dieser Antwort immer völlig dunkel !git rebase --onto <new-parent> <old-parent>
Sagte mir alles darüber, wie man das benutztrebase --onto
, was jede andere Frage und jedes andere Dokument, das ich bisher gelesen habe, nicht konnte.rebase this commit on that one
, odergit rebase --on <new-parent> <commit>
. Die Verwendung des alten Elternteils hier macht für mich keinen Sinn.git rebase <new-parent>
.Wenn sich herausstellt, dass Sie vermeiden müssen, die nachfolgenden Commits neu zu gründen (z. B. weil ein Umschreiben des Verlaufs unhaltbar wäre), können Sie den Git-Ersatz verwenden (verfügbar in Git 1.6.5 und höher).
Wenn die obige Ersetzung festgelegt ist, geben alle Anforderungen für das Objekt B tatsächlich das Objekt C zurück. Der Inhalt von C ist genau der gleiche wie der Inhalt von B, mit Ausnahme des ersten Elternteils (gleiche Eltern (außer dem ersten), des gleichen Baums). gleiche Festschreibungsnachricht).
Ersetzungen sind standardmäßig aktiv, können jedoch durch Verwenden der
--no-replace-objects
Option zum Git (vor dem Befehlsnamen) oder durch Festlegen derGIT_NO_REPLACE_OBJECTS
Umgebungsvariablen deaktiviert werden. Ersetzungen können durch Drückenrefs/replace/*
(zusätzlich zum Normalen) geteilt werdenrefs/heads/*
) geteilt werden.Wenn Ihnen das Commit-Munging (wie oben mit sed ausgeführt ) nicht gefällt , können Sie Ihr Ersatz-Commit mit Befehlen höherer Ebene erstellen:
Der große Unterschied besteht darin, dass diese Sequenz die zusätzlichen Eltern nicht weitergibt, wenn B ein Merge-Commit ist.
quelle
refs/replace/
. Wenn Sie dies nur einmal tun müssen, können Sie diesgit push yourremote 'refs/replace/*'
im Quell-Repository undgit fetch yourremote 'refs/replace/*:refs/replace/*'
in den Ziel-Repositorys tun . Wenn Sie dies mehrmals tun müssen, können Sie diese Referenzspezifikationen stattdessen einerremote.yourremote.push
Konfigurationsvariablen (im Quellrepository ) und einerremote.yourremote.fetch
Konfigurationsvariablen (in den Zielrepositorys) hinzufügen .git replace --grafts
. Ich bin mir nicht sicher, wann dies hinzugefügt wurde, aber sein ganzer Zweck besteht darin, einen Ersatz zu erstellen, der mit Commit B identisch ist, jedoch mit den angegebenen Eltern.Beachten Sie, dass zum Ändern eines Commits in Git alle darauf folgenden Commits geändert werden müssen. Dies wird nicht empfohlen, wenn Sie diesen Teil der Geschichte veröffentlicht haben und jemand seine Arbeit möglicherweise auf der Geschichte aufgebaut hat, die es vor der Änderung war.
Eine alternative Lösung,
git rebase
die in Bernsteins Antwort erwähnt wird, besteht darin, den Transplantationsmechanismus (siehe Definition von Git-Transplantaten im Git-Glossar und Dokumentation der.git/info/grafts
Datei in der Dokumentation zum Git-Repository-Layout ) zu verwenden, um das übergeordnete Element eines Commits zu ändern und zu überprüfen, ob es mit einem Verlaufs-Viewer (gitk
,git log --graph
usw.) und verwenden Sie danngit filter-branch
(wie im Abschnitt "Beispiele" der Manpage beschrieben), um es dauerhaft zu machen (und entfernen Sie dann das Transplantat und entfernen Sie optional die Original-Refs, die durch gesichert wurdengit filter-branch
das Repository , oder klonen Sie das Repository neu):HINWEIS !!! Diese Lösung unterscheidet sich von der Rebase-Lösung darin, dass Änderungen durch
git rebase
Rebase / Transplantation wiederhergestellt werden , während die auf Transplantaten basierende Lösung die Commits einfach so wie sie ist reparieren würde , ohne die Unterschiede zwischen alten und neuen Eltern zu berücksichtigen!quelle
git replace
Git-Transplantate ersetzt (vorausgesetzt, Sie haben Git 1.6.5 oder höher).git-replace
+ nicht verwendengit-filter-branch
? In meinem Testfilter-branch
schien der Ersatz zu respektieren und den gesamten Baum neu zu gründen.git filter-branch
schreibt die Geschichte neu, respektiert Transplantate und Ersetzungen (aber in der umgeschriebenen Geschichte werden Ersetzungen strittig sein); Push führt zu einer nicht fasf-forward Änderung.git replace
schafft vor Ort übertragbare Ersatzprodukte; Push wird schnell vorgespult, aber Sie müssten Push ausführenrefs/replace
, um Ersatz zu übertragen, um den Verlauf zu korrigieren. HTHUm die obigen Antworten zu klären und mein eigenes Skript schamlos einzustecken:
Es hängt davon ab, ob Sie "neu gründen" oder "reparieren" möchten. Eine Rebase , wie von Amber vorgeschlagen , bewegt sich um Unterschiede . Ein Reparent bewegt sich , wie von Jakub und Chris vorgeschlagen , um Schnappschüsse des gesamten Baumes. Wenn Sie reparieren möchten, empfehle ich,
git reparent
die Arbeit nicht manuell , sondern zu verwenden.Vergleich
Angenommen, Sie haben das Bild links und möchten, dass es wie das Bild rechts aussieht:
Sowohl das Rebasieren als auch das Reparentieren ergeben das gleiche Bild, aber die Definition von
C'
unterscheidet sich. Withgit rebase --onto A B
,C'
enthält keine Änderungen, die von eingeführt wurdenB
. Mitgit reparent -p A
,C'
wird identisch seinC
(außer , dassB
nicht in der Geschichte sein wird).quelle
reparent
Skript experimentiert . es funktioniert wunderbar; sehr zu empfehlen . Ich würde auch empfehlen, vor dem Aufruf ein neuesbranch
Label undcheckout
diesen Zweig zu erstellen (da sich das Zweigetikett verschiebt).Auf jeden Fall hat mir die Antwort von @ Jakub vor einigen Stunden geholfen, als ich genau das Gleiche wie das OP versuchte.
Allerdings
git replace --graft
ist jetzt die einfachere Lösung in Bezug auf Transplantationen. Ein Hauptproblem bei dieser Lösung war auch, dass ich durch jeden Filterzweig jeden Zweig verlor, der nicht mit dem HEAD-Zweig zusammengeführt wurde. Danngit filter-repo
hat der Job perfekt und fehlerfrei gemacht.Weitere Informationen: Lesen Sie den Abschnitt "Re-Grafting-Verlauf" in den Dokumenten
quelle