6424 Stimmen

Verschieben der letzten Übertragung(en) in einen neuen Zweig mit Git

Ich würde gerne die letzten Commits, die ich an Master übertragen habe, in einen neuen Zweig verschieben und Master auf den Stand vor diesen Commits zurückbringen. Leider ist mein Git-Fu noch nicht stark genug, kann mir jemand helfen?

D.h. Wie kann ich von diesem

master A - B - C - D - E

dazu?

newbranch     C - D - E
             /
master A - B

161 Stimmen

Anmerkung: Ich habe die gegenteilige Frage gestellt aquí

6 Stimmen

eddmann.com/posts/ dieser funktioniert

22 Stimmen

Wurden die Kommentare hier gelöscht? Ich frage, weil ich bei meinem zweimonatlichen Besuch dieser Frage immer an diesem Kommentar vorbeilaufe.

8244voto

sykora Punkte 88704

Umzug in eine bestehende Filiale

Wenn Sie Ihre Commits in ein bestehende Niederlassung wird es so aussehen:

git checkout existingbranch
git merge master
git checkout master
git reset --hard HEAD~3 # Go back 3 commits. You *will* lose uncommitted work.
git checkout existingbranch

Zuvor können Sie unbestätigte Bearbeitungen in Ihrem Stash speichern, indem Sie git stash . Nach der Fertigstellung können Sie die zwischengespeicherten unbestätigten Bearbeitungen mit git stash pop

Umzug in eine neue Niederlassung

WARNUNG: Diese Methode funktioniert, weil Sie mit dem ersten Befehl einen neuen Zweig erstellen: git branch newbranch . Wenn Sie Übertragungen in einen bestehende Niederlassung müssen Sie Ihre Änderungen in den bestehenden Zweig einfügen, bevor Sie die git reset --hard HEAD~3 (ver Umzug in eine bestehende Filiale oben). Wenn Sie Ihre Änderungen nicht zuerst zusammenführen, gehen sie verloren.

Sofern keine anderen Umstände vorliegen, kann dies leicht durch Verzweigung und Zurückrollen geschehen.

# Note: Any changes not committed will be lost.
git branch newbranch      # Create a new branch, saving the desired commits
git checkout master       # checkout master, this is the place you want to go back
git reset --hard HEAD~3   # Move master back by 3 commits (Make sure you know how many commits you need to go back)
git checkout newbranch    # Go to the new branch that still has the desired commits

Vergewissern Sie sich jedoch, wie viele Übertragungen Sie zurückgehen müssen. Alternativ können Sie anstelle von HEAD~3 geben Sie einfach den Hash der Übergabe an (oder die Referenz wie Ursprung/Master ), zu dem Sie "zurückkehren" möchten, auf der Meister (/Aktuell)-Zweig, z.B:

git reset --hard a1b2c3d4

*1 Sie werden sólo Sie werden Commits aus dem Master-Zweig "verlieren", aber keine Sorge, Sie werden diese Commits in newbranch haben!

Zum Schluss, Sie benötigen möglicherweise um Ihre letzten Änderungen in das Haupt-Repository zu pushen:

git push origin master --force

WARNUNG: Mit Git Version 2.0 und später, wenn Sie später git rebase den neuen Zweig auf den ursprünglichen ( master ) verzweigen, benötigen Sie möglicherweise eine explizite --no-fork-point um zu vermeiden, dass die übertragenen Übertragungen verloren gehen. Mit branch.autosetuprebase always Set macht dies wahrscheinlicher. Siehe Die Antwort von John Mellor für Details.

286 Stimmen

Und vor allem, no Versuchen Sie, weiter zurück zu gehen als bis zu dem Punkt, an dem Sie zuletzt Commits in ein anderes Repository verschoben haben, aus dem jemand anderes gezogen haben könnte.

128 Stimmen

Ich frage mich, ob Sie erklären können, WARUM das funktioniert. Für mich erstellst du einen neuen Zweig, entfernst 3 Commits aus dem alten Zweig, in dem du dich noch befindest, und checkst dann den Zweig aus, den du erstellt hast. Wie tauchen dann die entfernten Commits auf magische Weise im neuen Zweig auf?

162 Stimmen

@Jonathan Dumaine: Weil ich den neuen Zweig erstellt habe, bevor ich die Übertragungen aus dem alten Zweig entfernt habe. Sie sind immer noch im neuen Zweig vorhanden.

1243voto

Ryan Lundy Punkte 195074

Für diejenigen, die sich fragen, warum es funktioniert (wie ich anfangs):

Sie wollen zu C zurückkehren und D und E in den neuen Zweig verschieben. So sieht es zunächst aus:

A-B-C-D-E (HEAD)

      master

Nach git branch newBranch :

    newBranch

A-B-C-D-E (HEAD)

      master

Nach git reset --hard HEAD~2 :

    newBranch

A-B-C-D-E (HEAD)

  master

Da eine Verzweigung nur ein Zeiger ist, Meister verweist auf die letzte Übertragung. Wenn Sie newBranch haben Sie einfach einen neuen Zeiger auf die letzte Übertragung erstellt. Dann verwenden Sie git reset haben Sie den Meister Zeiger auf zwei Verpflichtungen zurück. Aber da Sie den Zeiger nicht verschoben haben newBranch verweist er immer noch auf die ursprüngliche Übertragung.

0 Stimmen

@Andrew Ohne --hard lässt es die Änderungen im Arbeitsverzeichnis. In diesem Fall können wir einen neuen Zweig erstellen und die Änderungen in den neuen Zweig übertragen. Würde dieser Ansatz die Notwendigkeit beseitigen, sicherzustellen, dass andere Ihre Änderungen nicht gezogen haben?

78 Stimmen

Ich musste auch eine git push origin master --force damit die Änderung im Haupt-Repository angezeigt wird.

0 Stimmen

Wahrscheinlich ist es eine gute Idee, die --force-with-lease für den Fall, dass sich bereits jemand auf der Grundlage Ihrer alten Kette verpflichtet hat.

589voto

Ivan Punkte 13352

Generell...

Die von sykora vorgestellte Methode ist in diesem Fall die beste Option. Aber manchmal ist es nicht die einfachste und es ist keine allgemeine Methode. Für eine allgemeine Methode verwenden Sie Git-Pick :

Um das zu erreichen, was OP möchte, ist ein 2-stufiger Prozess erforderlich:

Schritt 1 - Notieren Sie, welche Commits von Master Sie auf einer newbranch

Ausführen

git checkout master
git log

Notieren Sie die Hashes von (sagen wir 3) Übertragungen, die Sie auf newbranch . Hier werde ich verwenden:
C begehen: 9aa1233
D begehen: 453ac3d
E begehen: 612ecb3

Anmerkung: Sie können die ersten sieben Zeichen verwenden oder den gesamten Commit-Hash

Schritt 2 - Legen Sie sie auf die newbranch

git checkout newbranch
git cherry-pick 612ecb3
git cherry-pick 453ac3d
git cherry-pick 9aa1233

OR (auf Git 1.7.2+, verwenden Sie Bereiche)

git checkout newbranch
git cherry-pick 612ecb3~1..9aa1233

Git-Pick wendet diese drei Commits auf newbranch an.

2 Stimmen

Wenn die Cherry Picking ist die letzten N Commits dann können Sie einfach verschieben Sie Ihre Master HEAD zurück w/ git reset --hard HEAD~N

18 Stimmen

Dies funktioniert sehr gut, wenn Sie versehentlich den falschen Nicht-Master-Zweig übertragen, obwohl Sie einen neuen Funktionszweig hätten erstellen sollen.

8 Stimmen

Die Informationen über git cherry-pick sind gut, aber die Befehle in diesem Beitrag funktionieren nicht. 1) "git checkout newbranch" sollte "git checkout -b newbranch" lauten, da newbranch noch nicht existiert; 2) wenn man newbranch aus dem bestehenden Master-Branch auscheckt, sind die drei Commits bereits darin enthalten, es hat also keinen Sinn, sie zu wählen. Am Ende des Tages, um das zu bekommen, was der OP wollte, müssen Sie immer noch eine Form von reset --hard HEAD durchführen.

443voto

John Mellor Punkte 11906

Die meisten bisherigen Antworten sind gefährlich falsch!

Tun Sie dies NICHT:

git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch

Wenn Sie das nächste Mal git rebase 又は git pull --rebase ) würden diese 3 Übertragungen stillschweigend aus newbranch ! (siehe Erklärung unten)

Tun Sie stattdessen Folgendes:

git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
  • Zuerst werden die 3 letzten Übertragungen verworfen ( --keep ist wie --hard , aber sicherer, da es eher scheitert als unbestätigte Änderungen zu verwerfen).
  • Dann gabelt sie sich newbranch .
  • Dann wählt es diese 3 Commits zurück auf newbranch . Da sie nicht mehr durch einen Zweig referenziert werden, geschieht dies durch die Verwendung von git's reflog : HEAD@{2} ist die Übergabe, die HEAD vor 2 Operationen, d.h. bevor wir 1. ausgecheckt haben newbranch und 2. verwendet git reset um die 3 Commits zu verwerfen.

Warnung: Das Reflog ist standardmäßig aktiviert, aber wenn Sie es manuell deaktiviert haben (z.B. durch Verwendung eines "nackten" Git-Repositorys), werden Sie nicht in der Lage sein, die 3 Commits zurückzubekommen, nachdem Sie git reset --keep HEAD~3 .

Eine Alternative, die nicht auf das Reflog angewiesen ist, ist:

# newbranch will omit the 3 most recent commits.
git checkout -b newbranch HEAD~3
git branch --set-upstream-to=oldbranch
# Cherry-picks the extra commits from oldbranch.
git cherry-pick ..oldbranch
# Discards the 3 most recent commits from oldbranch.
git branch --force oldbranch oldbranch~3

(wenn Sie es vorziehen, können Sie schreiben @{-1} - den zuvor ausgecheckten Zweig - anstelle von oldbranch ).


Technische Erklärung

Warum sollte git rebase die 3 Übertragungen nach dem ersten Beispiel verwerfen? Es ist, weil git rebase ohne Argumente ermöglicht die --fork-point Option, die das lokale Reflog verwendet, um zu verhindern, dass der Upstream-Zweig gepusht wird.

Angenommen, Sie haben origin/master verzweigt, als es die Commits M1, M2 und M3 enthielt, und dann selbst drei Commits gemacht:

M1--M2--M3  <-- origin/master
         \
          T1--T2--T3  <-- topic

aber dann schreibt jemand die Geschichte um, indem er den Ursprung/Master zwingt, M2 zu entfernen:

M1--M3'  <-- origin/master
 \
  M2--M3--T1--T2--T3  <-- topic

Verwenden Sie Ihr lokales Reflog, git rebase kann sehen, dass Sie sich von einer früheren Inkarnation des origin/master-Zweigs abgespalten haben, und daher die M2 und M3 Commits nicht wirklich Teil Ihres Themenzweigs sind. Daher wird vernünftigerweise davon ausgegangen, dass Sie M2 nicht mehr in Ihrem Themenzweig haben wollen, sobald der Themenzweig rebasiert ist, da er aus dem Upstream-Zweig entfernt wurde:

M1--M3'  <-- origin/master
     \
      T1'--T2'--T3'  <-- topic (rebased)

Dieses Verhalten ist sinnvoll und in der Regel auch richtig, wenn es um eine Umbasierung geht.

Das ist der Grund, warum die folgenden Befehle fehlschlagen:

git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch

ist, weil sie das Reflog in einem falschen Zustand lassen. Git sieht newbranch als vom Upstream-Zweig mit einer Revision abgezweigt, die die 3 Commits enthält, dann wird die reset --hard schreibt die Upstream-Historie neu, um die Commits zu entfernen, so dass Sie beim nächsten Mal git rebase verwirft er sie wie jeden anderen Commit, der aus dem Upstream entfernt wurde.

Aber in diesem speziellen Fall wollen wir, dass diese 3 Commits als Teil des Themenzweigs betrachtet werden. Um das zu erreichen, müssen wir den Upstream bei der früheren Revision abzweigen, die die 3 Commits nicht enthält. Das ist es, was meine vorgeschlagenen Lösungen tun, daher lassen sie beide das Reflog im richtigen Zustand.

Für weitere Einzelheiten siehe die Definition von --fork-point im Git-Rebase et git merge-base docs.

0 Stimmen

Ich glaube, ich wäre mit dieser Antwort zufriedener, wenn Sie mir zeigen könnten, was der "falsche" Zustand von reflog im Gegensatz zum richtigen Zustand unter diesen Umständen wäre.

0 Stimmen

Wenn ich in Ihrem Szenario M2 und M3 in meinem Themenzweig behalte und dann versuche, meinen Themenzweig wieder mit dem Masterzweig zusammenzuführen, führe ich dann nicht die Bearbeitungen wieder ein, die jemand anderes zu löschen versucht hat (z. B. M2)? Es scheint, dass dies ein unerwünschtes Verhalten ist, oder? Für mich macht es Sinn, dass, wenn jemand die Geschichte neu schreibt, Sie wollen, dass Ihr Themenzweig die neue Geschichte verfolgt, damit Sie die Geschichte nicht wieder zurückschreiben, wenn Sie zusammenführen.

27 Stimmen

In dieser Antwort steht "Tun Sie das NICHT!" über etwas, das niemand vorgeschlagen hat.

399voto

aragaer Punkte 16174

Eine weitere Möglichkeit, dies zu tun, mit nur 2 Befehlen. Außerdem bleibt Ihr aktueller Arbeitsbaum intakt.

git checkout -b newbranch # switch to a new branch
git branch -f master HEAD~3 # make master point to some older commit

Alte Fassung - bevor ich erfuhr, dass git branch -f

git checkout -b newbranch # switch to a new branch
git push . +HEAD~3:master # make master point to some older commit 

In der Lage zu sein push zu . ist ein netter Trick, den man kennen sollte.

1 Stimmen

Aktuelles Verzeichnis. Ich vermute, dass dies nur funktioniert, wenn Sie sich in einem Hauptverzeichnis befinden.

1 Stimmen

Der lokale Vorstoß regt zum Schmunzeln an, aber wenn man darüber nachdenkt, wie unterscheidet er sich von git branch -f hier?

3 Stimmen

@GerardSexton . ist der aktuelle Director. git kann zu REMOTES oder GIT-URLs pushen. path to local directory wird die Git-URLs-Syntax unterstützt. Siehe den Abschnitt GIT-URLS in git help clone .

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X