Antworten:
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 benutzt rebase --onto
, was jede andere Frage und jedes andere Dokument, das ich bisher gelesen habe, nicht konnte.
rebase this commit on that one
, oder git 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).
# …---o---A---o---o---…
#
# …---o---B---b---b---…
#
# We want to transplant B to be "on top of" A.
# The tree of descendants from B (and A) can be arbitrarily complex.
replace_first_parent() {
old_parent=$(git rev-parse --verify "${1}^1") || return 1
new_parent=$(git rev-parse --verify "${2}^0") || return 2
new_commit=$(
git cat-file commit "$1" |
sed -e '1,/^$/s/^parent '"$old_parent"'$/parent '"$new_parent"'/' |
git hash-object -t commit -w --stdin
) || return 3
git replace "$1" "$new_commit"
}
replace_first_parent B A
# …---o---A---o---o---…
# \
# C---b---b---…
#
# C is the replacement for B.
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 der GIT_NO_REPLACE_OBJECTS
Umgebungsvariablen deaktiviert werden. Ersetzungen können durch Drücken refs/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:
git checkout B~0
git reset --soft A
git commit -C B
git replace B HEAD
git checkout -
Der große Unterschied besteht darin, dass diese Sequenz die zusätzlichen Eltern nicht weitergibt, wenn B ein Merge-Commit ist.
refs/replace/
. Wenn Sie dies nur einmal tun müssen, können Sie dies git push yourremote 'refs/replace/*'
im Quell-Repository und git fetch yourremote 'refs/replace/*:refs/replace/*'
in den Ziel-Repositorys tun . Wenn Sie dies mehrmals tun müssen, können Sie diese Referenzspezifikationen stattdessen einer remote.yourremote.push
Konfigurationsvariablen (im Quellrepository ) und einer remote.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 dann git 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):
Echo "$ commit-id $ transplant-id" >> .git / info / transplants Git Filter-Zweig $ Graft-ID..HEAD
HINWEIS !!! Diese Lösung unterscheidet sich von der Rebase-Lösung darin, dass Änderungen durchgit 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!
git replace
Git-Transplantate ersetzt (vorausgesetzt, Sie haben Git 1.6.5 oder höher).
git-replace
+ nicht verwenden git-filter-branch
? In meinem Test filter-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ühren refs/replace
, um Ersatz zu übertragen, um den Verlauf zu korrigieren. HTH
Um 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.
Angenommen, Sie haben das Bild links und möchten, dass es wie das Bild rechts aussieht:
C'
/
A---B---C A---B---C
Sowohl das Rebasieren als auch das Reparentieren ergeben das gleiche Bild, aber die Definition von C'
unterscheidet sich. With git rebase --onto A B
, C'
enthält keine Änderungen, die von eingeführt wurden B
. Mit git reparent -p A
, C'
wird identisch sein C
(außer , dass B
nicht in der Geschichte sein wird).
reparent
Skript experimentiert . es funktioniert wunderbar; sehr zu empfehlen . Ich würde auch empfehlen, vor dem Aufruf ein neues branch
Label und checkout
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. Dann git filter-repo
hat der Job perfekt und fehlerfrei gemacht.
$ git replace --graft <commit> <new parent commit>
$ git filter-repo --force
Weitere Informationen: Lesen Sie den Abschnitt "Re-Grafting-Verlauf" in den Dokumenten