Wie kann ein Commit zwischen zwei beliebigen Commits in der Vergangenheit eingefügt werden?

126

Angenommen, ich habe den folgenden Commit-Verlauf in meinem Zweig nur vor Ort:

A -- B -- C

Wie füge ich ein neues Commit zwischen Aund ein B?

BartoszKP
quelle
3
Commit namens Ą ? :)
Antek
Ich habe eine sehr ähnliche Frage, wie man in der Vergangenheit eine neue Version anstelle eines Commits einfügt.
Pavel P

Antworten:

177

Es ist noch einfacher als in der Antwort von OP.

  1. git rebase -i <any earlier commit>. Dies zeigt eine Liste der Commits in Ihrem konfigurierten Texteditor an.
  2. Suchen Sie das Commit, nach dem Sie einfügen möchten (nehmen wir an, es ist a1b2c3d). Ändern Sie pickin Ihrem Editor für diese Zeile in edit.
  3. Beginnen Sie die Rebase, indem Sie Ihren Texteditor schließen (speichern Sie Ihre Änderungen). Dadurch bleiben Sie an einer Eingabeaufforderung mit dem zuvor ausgewählten Commit ( a1b2c3d), als wäre es gerade festgeschrieben worden .
  4. Nehmen Sie Ihre Änderungen vor und git commit( NICHT im Gegensatz zu den meisten anderen edit). Dadurch wird nach dem von Ihnen ausgewählten Commit ein neues Commit erstellt .
  5. git rebase --continue. Dadurch werden die aufeinanderfolgenden Commits wiederholt, und Ihr neues Commit wird an der richtigen Stelle eingefügt.

Beachten Sie, dass dies die Geschichte neu schreibt und jeden anderen bricht, der versucht zu ziehen.

SLaks
quelle
1
Dies fügte das neue Commit nach dem Commit hinzu, dh nach dem, auf das ich neu basiert (auch das letzte Commit), anstatt direkt nach dem, auf das ich neu basiert habe. Das Ergebnis war das gleiche, als hätte ich am Ende einfach ein neues Commit mit den Änderungen vorgenommen, die ich einfügen wollte. Meine Geschichte wurde A -- B -- C -- Dstatt gewünscht A -- D -- B -- C.
XedinUnknown
2
@XedinUnknown: Dann haben Sie Rebase nicht richtig verwendet.
SLaks
3
Jetzt Dkönnte überall ein Commit sein. Nehmen wir an, wir haben A - B - Cund wir haben ein Commit, Ddas nicht einmal in diesem Zweig ist. Wir wissen, dass es SHA ist, aber wir können es tun git rebase -i HEAD~3. Jetzt zwischen den Aund B pickLinien, legen wir eine neue pick Linie , die sagt pick SHA, den Hash des gewünschten geben D. Es muss nicht der vollständige Hash sein, sondern nur der verkürzte. git rebase -iNur Cherry wählt die Commits aus, die durch pickZeilen im Puffer aufgelistet sind . Sie müssen nicht die Originale sein, die für Sie aufgelistet sind.
Kaz
1
@Kaz Das sieht nach einer anderen, gültigen Antwort aus.
BartoszKP
3
Noch einfacher ist es, das breakSchlüsselwort im Editor in einer eigenen Zeile zwischen zwei Commits zu verwenden (oder in der ersten Zeile, um ein Commit vor dem angegebenen Commit einzufügen).
SimonT
30

Es stellt sich als recht einfach heraus, die Antwort hier zu finden . Angenommen, Sie befinden sich in einem Zweig branch. Führen Sie die folgenden Schritte aus:

  • Erstellen Sie einen temporären Zweig aus dem Commit, nachdem Sie das neue Commit einfügen möchten (in diesem Fall Commit A):

    git checkout -b temp A
    
  • Führen Sie die Änderungen durch und schreiben Sie sie fest. Erstellen Sie ein Commit. Nennen wir es N:

    git commit -a -m "Message"
    

    (oder git addgefolgt von git commit)

  • Basen Sie die Commits, die Sie nach dem neuen Commit (in diesem Fall Commits Bund C) haben möchten, auf das neue Commit zurück:

    git rebase temp branch
    

(Möglicherweise müssen Sie diese verwenden -p, um Zusammenführungen beizubehalten, falls vorhanden - dank eines nicht mehr vorhandenen Kommentars von ciekawy )

  • Löschen Sie den temporären Zweig:

    git branch -d temp
    

Danach sieht der Verlauf wie folgt aus:

A -- N -- B -- C

Es ist natürlich möglich, dass beim erneuten Basieren einige Konflikte auftreten.

Wenn Ihre Niederlassung nicht nur lokal ist, führt dies zum Umschreiben des Verlaufs und kann daher zu ernsthaften Problemen führen.

BartoszKP
quelle
2
Ich konnte der akzeptierten Antwort von SLaks nicht folgen, aber das funktionierte für mich. Nachdem ich den gewünschten Commit-Verlauf erhalten hatte, musste ich git push --forcedas Remote-Repo ändern.
Fluchtzeichen
1
Wenn Sie rebase mit der Option -Xtheirs verwenden, werden Konflikte automatisch korrekt gelöst git rebase temp branch -Xtheirs. Hilfreiche Antwort zum Einfügen in ein Skript!
David C
Für Noobs wie mich möchte ich das nachher hinzufügen git rebase temp branch, aber vorher git branch -d tempmüssen Sie nur Konflikte und Probleme beheben und inszenieren git rebase --continue, dh Sie müssen nichts begehen usw.
Pugsley
19

Noch einfachere Lösung:

  1. Erstellen Sie am Ende Ihr neues Commit, D. Jetzt haben Sie:

    A -- B -- C -- D
    
  2. Dann renne:

    $ git rebase -i hash-of-A
    
  3. Git öffnet Ihren Editor und sieht folgendermaßen aus:

    pick 8668d21 B
    pick 650f1fc C
    pick 74096b9 D
    
  4. Bewegen Sie D einfach so nach oben, speichern Sie es und beenden Sie es

    pick 74096b9 D
    pick 8668d21 B
    pick 650f1fc C
    
  5. Jetzt haben Sie:

    A -- D -- B -- C
    
Matthew
quelle
6
Gute Idee, aber es kann schwierig sein, D auf C einzuführen, wenn Sie beabsichtigen, diese Änderungen vorzunehmen. an A.
BartoszKP
Ich habe eine Situation, in der ich 3 Commits habe, die ich zusammen neu aufbauen möchte, und ein Commit in der Mitte, das nichts damit zu tun hat. Dies ist super schön, wenn Sie dieses Commit früher oder später in der Reihe der Commits verschieben können.
Unflores
13

Angenommen, der Commit-Verlauf lautet wie folgt: preA -- A -- B -- CWenn Sie einen Commit zwischen Aund einfügen möchten, Blauten die Schritte wie folgt:

  1. git rebase -i hash-of-preA

  2. Git öffnet Ihren Editor. Der Inhalt könnte folgendermaßen aussehen:

    pick 8668d21 A
    pick 650f1fc B
    pick 74096b9 C
    

    Ändern Sie die erste pickin edit:

    edit 8668d21 A
    pick 650f1fc B
    pick 74096b9 C
    

    Speichern und schließen.

  3. Ändern Sie Ihren Code und dann git add . && git commit -m "I"

  4. git rebase --continue

Jetzt ist Ihr Git-Commit-Verlauf preA -- A -- I -- B -- C


Wenn Sie auf einen Konflikt stoßen, stoppt Git bei diesem Commit. Sie können git diffKonfliktmarkierungen suchen und lösen. Nachdem Sie alle Konflikte git add <filename>gelöst haben , müssen Sie Git mitteilen, dass der Konflikt gelöst wurde, und anschließend erneut ausführen git rebase --continue.

Wenn Sie die Rebase rückgängig machen möchten, verwenden Sie git rebase --abort.

haolee
quelle
10

Hier ist eine Strategie, die es vermeidet, während der Rebase einen "Edit-Hack" durchzuführen, wie in den anderen Antworten zu sehen ist, die ich gelesen habe.

Durch die Verwendung erhalten git rebase -iSie eine Liste der Commits seit diesem Commit. Fügen Sie einfach oben in der Datei einen "break" hinzu. Dadurch wird die Rebase an diesem Punkt unterbrochen.

break
pick <B's hash> <B's commit message>
pick <C's hash> <C's commit message>

Einmal gestartet, git rebasestoppt nun an der Stelle der "Pause". Sie können jetzt Ihre Dateien bearbeiten und Ihr Commit normal erstellen. Sie können dann die Rebase mit fortsetzen git rebase --continue. Dies kann zu Konflikten führen, die Sie beheben müssen. Wenn Sie sich verlaufen, vergessen Sie nicht, dass Sie jederzeit mit abbrechen können git rebase --abort.

Diese Strategie kann verallgemeinert werden, um ein Commit an einer beliebigen Stelle einzufügen. Setzen Sie einfach die "Pause" an die Stelle, an der Sie ein Commit einfügen möchten.

Vergessen Sie nicht, nach dem Umschreiben der Geschichte git push -f. Es gelten die üblichen Warnungen vor anderen Personen, die Ihre Filiale abrufen.

Axerologementie
quelle
Entschuldigung, aber ich habe Probleme zu verstehen, wie dies "Vermeiden von Rebase" ist. Sie werden laufen rebasehier. Es ist kein großer Unterschied, ob Sie das Commit während der Rebase oder im Voraus erstellen.
BartoszKP
Woops, ich wollte den "Edit Hack" während der Rebase vermeiden, ich denke, ich habe das schlecht formuliert.
Axerologementy
Richtig. Meine Antwort verwendet auch nicht die "Bearbeiten" -Funktion von Rebase. Dies ist jedoch ein weiterer gültiger Ansatz - danke! :-)
BartoszKP
6

Viele gute Antworten hier schon. Ich wollte nur eine "No Rebase" -Lösung in 4 einfachen Schritten hinzufügen.


Zusammenfassung

git checkout A
git commit -am "Message for commit D"
git cherry-pick A..C
git branch -f master HEAD

Erläuterung

(Hinweis: Ein Vorteil dieser Lösung besteht darin, dass Sie Ihren Zweig erst im letzten Schritt berühren, wenn Sie zu 100% sicher sind, dass Sie mit dem Endergebnis einverstanden sind, sodass Sie einen sehr praktischen Schritt zur "Vorbestätigung" haben AB-Tests zulassen .)


Ausgangszustand (ich habe masterfür Ihren Filialnamen angenommen )

A -- B -- C <<< master <<< HEAD

1) Richten Sie den KOPF zunächst auf die richtige Stelle

git checkout A

     B -- C <<< master
    /
   A  <<< detached HEAD

(Optional hätten wir hier anstelle von HEAD einen temporären Zweig erstellen können, mit git checkout -b temp Adem wir am Ende des Prozesses löschen müssten. Beide Varianten funktionieren nach Ihren Wünschen, da alles andere gleich bleibt.)


2) Erstellen Sie das neue Commit D, das eingefügt werden soll

# at this point, make the changes you wanted to insert between A and B, then

git commit -am "Message for commit D"

     B -- C <<< master
    /
   A -- D <<< detached HEAD (or <<< temp <<< HEAD)

3) Bringen Sie dann Kopien der letzten fehlenden Commits B und C mit (wäre dieselbe Zeile, wenn es mehr Commits gäbe).

git cherry-pick A..C

# (if any, resolve any potential conflicts between D and these last commits)

     B -- C <<< master
    /
   A -- D -- B' -- C' <<< detached HEAD (or <<< temp <<< HEAD)

(bequemer AB-Test hier bei Bedarf)

Jetzt ist der Moment gekommen, um Ihren Code zu überprüfen, alles zu testen, was getestet werden muss, und Sie können auch unterscheiden / vergleichen / überprüfen, was Sie hatten und was Sie nach den Operationen erhalten würden.


4) Abhängig von Ihren Tests zwischen Cund C'ist entweder OK oder KO.

(BEIDES) 4-OK) Bewegen Sie zum Schluss den Ref vonmaster

git branch -f master HEAD

     B -- C <<< (B and C are candidates for garbage collection)
    /
   A -- D -- B' -- C' <<< master

(ODER) 4-KO) Einfach masterunverändert lassen

Wenn Sie einen temporären Zweig erstellt haben, löschen Sie ihn einfach mit git branch -d <name>. Wenn Sie sich jedoch für die getrennte HEAD-Route entschieden haben und zu diesem Zeitpunkt überhaupt keine Aktion erforderlich ist, können die neuen Commits unmittelbar nach dem erneuten Anhängen HEADmit a für die Speicherbereinigung verwendet werdengit checkout master

In beiden Fällen (OK oder KO) müssen Sie an dieser Stelle mastererneut auschecken, um die Verbindung wiederherzustellen HEAD.

RomainValeri
quelle