Kasse eines anderen Zweigs auschecken, wenn nicht festgeschriebene Änderungen am aktuellen Zweig vorgenommen wurden

349

Die meiste Zeit, wenn ich versuche, einen anderen vorhandenen Zweig auszuchecken, erlaubt mir Git nicht, wenn ich einige nicht festgeschriebene Änderungen am aktuellen Zweig habe. Also muss ich diese Änderungen zuerst festschreiben oder aufbewahren.

Gelegentlich erlaubt mir Git jedoch, einen anderen Zweig auszuchecken, ohne diese Änderungen festzuschreiben oder zu speichern, und diese Änderungen werden in den Zweig übertragen, den ich auschecke.

Was ist hier die Regel? Ist es wichtig, ob die Änderungen bereitgestellt oder nicht bereitgestellt werden? Das Übertragen der Änderungen in einen anderen Zweig macht für mich keinen Sinn. Warum lässt Git das manchmal zu? Das heißt, ist es in einigen Situationen hilfreich?

Xufeng
quelle

Antworten:

350

Vorbemerkungen

Die Beobachtung hier ist, dass Sie, nachdem Sie mit der Arbeit begonnen haben branch1(vergessen oder nicht bemerkt haben, dass es gut wäre, zuerst zu einem anderen Zweig zu wechseln branch2):

git checkout branch2

Manchmal sagt Git "OK, du bist jetzt auf branch2!" Manchmal sagt Git: "Das kann ich nicht, ich würde einige deiner Änderungen verlieren."

Wenn Git dies nicht zulässt, müssen Sie Ihre Änderungen festschreiben, um sie an einem dauerhaften Ort zu speichern. Möglicherweise möchten Sie sie git stashzum Speichern verwenden. Dies ist eines der Dinge, für die es entwickelt wurde. Beachten Sie, dass git stash saveoder git stash pushtatsächlich bedeutet "Übernehmen Sie alle Änderungen, aber überhaupt keinen Zweig, und entfernen Sie sie dann von meinem derzeitigen Standort." Das macht es möglich zu wechseln: Sie haben jetzt keine laufenden Änderungen. Sie können sie dann git stash applynach dem Umschalten.

Seitenleiste: git stash saveist die alte Syntax; git stash pushwurde in Git Version 2.13 eingeführt, um einige Probleme mit den Argumenten zu beheben git stashund neue Optionen zuzulassen. Beide machen das Gleiche, wenn sie auf grundlegende Weise verwendet werden.

Sie können hier aufhören zu lesen, wenn Sie möchten!

Wenn Git nicht Sie lassen wechseln, haben Sie bereits eine Abhilfe: Verwendung git stashoder git commit; oder, wenn Ihre Änderungen trivial neu zu erstellen sind, verwenden Sie git checkout -f, um sie zu erzwingen. Bei dieser Antwort geht es darum, wann Git Sie git checkout branch2zulässt, obwohl Sie einige Änderungen vorgenommen haben. Warum funktioniert es manchmal und nicht zu anderen Zeiten?

Die Regel hier ist in einer Hinsicht einfach und in einer anderen kompliziert / schwer zu erklären:

Sie können Zweige mit nicht festgeschriebenen Änderungen im Arbeitsbaum genau dann wechseln, wenn für diese Umschaltung keine Änderungen erforderlich sind.

Das heißt - und bitte beachten Sie, dass dies immer noch vereinfacht ist; Es gibt einige besonders schwierige Eckfälle mit inszenierten git adds, git rms und dergleichen - nehmen wir an, Sie sind an branch1. A git checkout branch2müsste dies tun:

  • Für jede Datei , die ist in branch1und nicht in branch2, 1 zu entfernen , die Datei.
  • Erstellen Sie für jede Datei, die sich in branch2und nicht in branch1befindet, diese Datei (mit entsprechendem Inhalt).
  • branch2Aktualisieren Sie für jede Datei in beiden Zweigen die Arbeitsbaumversion , wenn die Version in unterschiedlich ist.

Jeder dieser Schritte kann etwas in Ihrem Arbeitsbaum blockieren:

  • Das Entfernen einer Datei ist "sicher", wenn die Version im Arbeitsbaum mit der festgeschriebenen Version in branch1übereinstimmt. Es ist "unsicher", wenn Sie Änderungen vorgenommen haben.
  • Das Erstellen einer Datei in der Art, wie sie angezeigt wird, branch2ist "sicher", wenn sie jetzt nicht vorhanden ist. 2 Es ist "unsicher", wenn es jetzt existiert, aber den "falschen" Inhalt hat.
  • Und natürlich ist das Ersetzen der Arbeitsbaumversion einer Datei durch eine andere Version "sicher", wenn die Arbeitsbaumversion bereits festgeschrieben ist branch1.

Das Erstellen eines neuen Zweigs ( git checkout -b newbranch) gilt immer als "sicher": Im Rahmen dieses Prozesses werden im Arbeitsbaum keine Dateien hinzugefügt, entfernt oder geändert, und der Index- / Staging-Bereich bleibt ebenfalls unberührt. (Vorsichtsmaßnahme: Es ist sicher, wenn Sie einen neuen Zweig erstellen, ohne den Startpunkt des neuen Zweigs zu ändern. Wenn Sie jedoch ein anderes Argument hinzufügen, z. B. git checkout -b newbranch different-start-pointmuss dies möglicherweise geändert werden, um zu wechseln different-start-point. Git wendet dann die Sicherheitsregeln für das Auschecken wie gewohnt an .)


1 Dies erfordert , dass wir definieren , was es bedeutet , für eine Datei in einem Zweig zu sein, was wiederum das Wort erfordert die Definition Zweig richtig. (Siehe auch Was genau meinen wir mit „Zweig“? ) Hier , was ich wirklich meinen , ist die sich verpflichten, der die Zweignamen aufgelöst wird : eine Datei , deren Pfad ist in , wenn ein Hash erzeugt. Diese Datei ist nicht in , wenn Sie stattdessen eine Fehlermeldung erhalten. Das Vorhandensein eines Pfads in Ihrem Index oder Arbeitsbaum ist für die Beantwortung dieser speziellen Frage nicht relevant. Das Geheimnis hier ist also, das Ergebnis von jedem zu untersuchenP branch1git rev-parse branch1:Pbranch1Pgit rev-parsebranch-name:path. Dies schlägt entweder fehl, weil sich die Datei höchstens in einem Zweig befindet, oder es werden zwei Hash-IDs angegeben. Wenn die beiden Hash-IDs identisch sind , ist die Datei in beiden Zweigen identisch. Es ist keine Änderung erforderlich. Wenn sich die Hash-IDs unterscheiden, unterscheidet sich die Datei in den beiden Zweigen und muss geändert werden, um die Zweige zu wechseln.

Der Schlüsselbegriff hier ist, dass Dateien in Commits für immer eingefroren sind. Dateien, die Sie bearbeiten, werden offensichtlich nicht eingefroren. Zumindest anfangs betrachten wir nur die Nichtübereinstimmungen zwischen zwei eingefrorenen Commits. Leider haben wir-oder Git-auch mit Dateientun habendie nicht sind in der Commit Sie von wechseln weg gehst und sind in der CommitSchalter auf Sie gehen. Dies führt zu den verbleibenden Komplikationen, da Dateien auch im Index und / oder im Arbeitsbaum vorhanden sein können, ohne dass diese beiden bestimmten eingefrorenen Commits vorhanden sein müssen, mit denen wir arbeiten.

2 Es könnte als "irgendwie sicher" angesehen werden, wenn es bereits mit dem "richtigen Inhalt" existiert, so dass Git es doch nicht erstellen muss. Ich erinnere mich an zumindest einige Versionen von Git, die dies zulassen, aber das Testen zeigt, dass es in Git 1.8.5.4 als "unsicher" eingestuft wird. Das gleiche Argument würde für eine geänderte Datei gelten, die zufällig geändert wird, um mit dem Zweig übereinzustimmen, zu dem gewechselt werden soll. Wiederum sagt 1.8.5.4 nur "würde überschrieben werden". Siehe auch das Ende der technischen Hinweise: Mein Speicher ist möglicherweise fehlerhaft, da ich nicht glaube, dass sich die Regeln für den Lesebaum geändert haben, seit ich Git in Version 1.5.something zum ersten Mal verwendet habe.


Ist es wichtig, ob die Änderungen bereitgestellt oder nicht bereitgestellt werden?

Ja, in gewisser Weise. Insbesondere können Sie eine Änderung vornehmen und dann die Arbeitsbaumdatei "de-modifizieren". Hier ist eine Datei in zwei Zweigen, die sich in branch1und unterscheidet branch2:

$ git show branch1:inboth
this file is in both branches
$ git show branch2:inboth
this file is in both branches
but it has more stuff in branch2 now
$ git checkout branch1
Switched to branch 'branch1'
$ echo 'but it has more stuff in branch2 now' >> inboth

Zu diesem Zeitpunkt inbothstimmt die Arbeitsbaumdatei mit der in überein branch2, obwohl wir aktiv sind branch1. Diese Änderung wird nicht für das Festschreiben bereitgestellt, was git status --shorthier gezeigt wird:

$ git status --short
 M inboth

Das Leerzeichen-dann-M bedeutet "modifiziert, aber nicht inszeniert" (oder genauer gesagt, die Arbeitsbaumkopie unterscheidet sich von der inszenierten / Indexkopie).

$ git checkout branch2
error: Your local changes ...

OK, jetzt inszenieren wir die Arbeitsbaumkopie, von der wir bereits wissen, dass sie auch mit der Kopie übereinstimmt branch2.

$ git add inboth
$ git status --short
M  inboth
$ git checkout branch2
Switched to branch 'branch2'

Hier stimmten die inszenierten und funktionierenden Kopien mit den darin enthaltenen überein branch2, sodass das Auschecken erlaubt war.

Versuchen wir einen weiteren Schritt:

$ git checkout branch1
Switched to branch 'branch1'
$ cat inboth
this file is in both branches

Die Änderung, die ich vorgenommen habe, geht jetzt im Staging-Bereich verloren (da der Checkout durch den Staging-Bereich schreibt). Dies ist ein bisschen wie ein Eckfall. Die Änderung ist nicht weg, aber die Tatsache, dass ich sie inszeniert hatte, ist weg.

Lassen Sie uns eine dritte Variante der Datei inszenieren, die sich von beiden Zweigkopien unterscheidet, und dann die Arbeitskopie so einstellen, dass sie mit der aktuellen Zweigversion übereinstimmt:

$ echo 'staged version different from all' > inboth
$ git add inboth
$ git show branch1:inboth > inboth
$ git status --short
MM inboth

Die beiden Ms bedeuten hier: Die bereitgestellte Datei unterscheidet sich von der HEADDatei, und die Arbeitsbaumdatei unterscheidet sich von der bereitgestellten Datei. Die Working-Tree-Version stimmt mit der branch1(aka HEAD) -Version überein :

$ git diff HEAD
$

Aber git checkoutdie Kasse nicht zulassen , dass:

$ git checkout branch2
error: Your local changes ...

Stellen wir die branch2Version als Arbeitsversion ein:

$ git show branch2:inboth > inboth
$ git status --short
MM inboth
$ git diff HEAD
diff --git a/inboth b/inboth
index ecb07f7..aee20fb 100644
--- a/inboth
+++ b/inboth
@@ -1 +1,2 @@
 this file is in both branches
+but it has more stuff in branch2 now
$ git diff branch2 -- inboth
$ git checkout branch2
error: Your local changes ...

Obwohl die aktuelle Arbeitskopie mit der in übereinstimmt, funktioniert branch2die bereitgestellte Datei nicht, sodass a git checkoutdiese Kopie verlieren würde und die git checkoutabgelehnt wird.

Technische Hinweise - nur für wahnsinnig Neugierige :-)

Der zugrunde liegende Implementierungsmechanismus für all dies ist der Git- Index . Der Index, auch als "Staging-Bereich" bezeichnet, ist der Ort, an dem Sie das nächste Commit erstellen : Er beginnt mit dem aktuellen Commit, dh mit dem, was Sie jetzt ausgecheckt haben, und jedes Mal, wenn Sie git addeine Datei erstellen, ersetzen Sie die Indexversion mit allem, was Sie in Ihrem Arbeitsbaum haben.

Denken Sie daran, die Arbeit Baum ist , wo Sie auf Ihre Dateien. Hier haben sie ihre normale Form und keine spezielle Form, die nur für Git nützlich ist, wie dies bei Commits und im Index der Fall ist. Sie extrahieren also eine Datei aus einem Commit über den Index und dann weiter in den Arbeitsbaum. Nachdem Sie git addes geändert haben, können Sie es in den Index aufnehmen. Es gibt also tatsächlich drei Stellen für jede Datei: das aktuelle Commit, den Index und den Arbeitsbaum.

Wenn Sie ausführen git checkout branch2, vergleicht Git unter den Deckblättern das Tip-Commit mit dem branch2, was sich jetzt sowohl im aktuellen Commit als auch im Index befindet. Jede Datei, die mit der aktuellen übereinstimmt, kann Git in Ruhe lassen. Es ist alles unberührt. Jede Datei, die in beiden Commits gleich ist , kann Git auch in Ruhe lassen - und mit diesen können Sie die Zweige wechseln.

Ein Großteil von Git, einschließlich Commit-Switching, ist aufgrund dieses Index relativ schnell . Was tatsächlich im Index enthalten ist, ist nicht jede Datei selbst, sondern der Hash jeder Datei . Die Kopie der Datei selbst wird als das, was Git als Blob-Objekt bezeichnet , im Repository gespeichert. Dies ähnelt der Speicherung der Dateien in Commits: Commits enthalten die Dateien nicht , sondern führen Git nur zur Hash-ID jeder Datei. So Git Hash - IDs-aktuell vergleichen kann 160-Bit-lange Strings zu entscheiden , ob Commits X und Y die haben gleiche Datei oder nicht. Diese Hash-IDs können dann auch mit der Hash-ID im Index verglichen werden.

Dies führt zu allen oben genannten seltsamen Eckfällen. Wir haben Commits X und Y , die beide eine Datei haben path/to/name.txt, und wir haben einen Indexeintrag für path/to/name.txt. Vielleicht stimmen alle drei Hashes überein. Vielleicht passen zwei von ihnen zusammen und einer nicht. Vielleicht sind alle drei unterschiedlich. Und vielleicht haben wir das auch another/file.txtnur in X oder nur in Y und ist oder ist jetzt nicht im Index. Jede dieser verschiedenen Fällen benötigt eine eigene separate Betrachtung: Ist Git Notwendigkeit , die Datei aus begehen zu indizieren zu kopieren, oder entfernen Sie sie aus dem Index, um Wechsel von X zu Y ? Wenn ja, ist es auch mussKopieren Sie die Datei in den Arbeitsbaum oder entfernen Sie sie aus dem Arbeitsbaum. Und wenn das ‚den Fall s, hatte der Index und Arbeit Baum Versionen bessere Übereinstimmung zumindest einer der engagierten Versionen; Andernfalls wird Git einige Daten verschlingen.

(Die vollständigen Regeln für all dies sind in nicht der git checkouterwarteten Dokumentation, sondern in der git read-treeDokumentation unter dem Abschnitt "Two Tree Merge" beschrieben .)

torek
quelle
3
... gibt es auch git checkout -m, die Ihre Arbeitsbaum- und Indexänderungen in der neuen Kasse zusammenführt.
Bis zum
1
Vielen Dank für diese hervorragende Erklärung! Aber wo finde ich die Informationen in den offiziellen Dokumenten? Oder sind sie unvollständig? Wenn ja, wie lautet die maßgebliche Referenz für git (hoffentlich anders als der Quellcode)?
Max
1
(1) Sie können nicht und (2) den Quellcode. Das Hauptproblem ist, dass sich Git ständig weiterentwickelt. Zum Beispiel gibt es momentan einen großen Schub, SHA-1 mit oder zugunsten von SHA-256 zu erweitern oder loszuwerden. Dieser spezielle Teil von Git ist jedoch schon seit langer Zeit ziemlich stabil, und der zugrunde liegende Mechanismus ist unkompliziert: Git vergleicht den aktuellen Index mit den aktuellen und den Ziel-Commits und entscheidet basierend auf dem Ziel-Commit, welche Dateien (falls vorhanden) geändert werden sollen testet dann die "Sauberkeit" von Arbeitsbaumdateien, wenn der Indexeintrag ersetzt werden müsste.
Torek
6
Kurze Antwort: Es gibt eine Regel, aber sie ist zu stumpf für den Durchschnittsbenutzer, um Hoffnung auf Verständnis zu haben, geschweige denn sich zu erinnern. Statt sich auf das Tool zu verlassen, um sich verständlich zu verhalten, sollten Sie sich stattdessen auf die disziplinierte Konvention verlassen, nur auszuchecken, wenn Sie Die aktuelle Niederlassung ist festgeschrieben und sauber. Ich verstehe nicht, wie dies die Frage beantwortet, wann es jemals nützlich sein würde, ausstehende Änderungen auf einen anderen Zweig zu übertragen, aber ich habe es möglicherweise verpasst, weil ich Schwierigkeiten habe, es zu verstehen.
Neutrino
2
@ HawkeyeParker: Diese Antwort wurde zahlreichen Änderungen unterzogen, und ich bin mir nicht sicher, ob eine davon sie erheblich verbessert hat, aber ich werde versuchen, etwas darüber hinzuzufügen, was es bedeutet, dass sich eine Datei "in einem Zweig" befindet. Letztendlich wird dies wackelig sein, weil der Begriff "Zweig" hier zunächst nicht richtig definiert ist, aber das ist noch ein weiterer Punkt.
Torek
50

Sie haben zwei Möglichkeiten: Verstecken Sie Ihre Änderungen:

git stash

dann später, um sie zurückzubekommen:

git stash apply

oder platzieren Sie Ihre Änderungen in einem Zweig, damit Sie den Remote-Zweig abrufen und Ihre Änderungen darauf zusammenführen können. Das ist eines der größten Dinge an git: Sie können einen Zweig erstellen, ihn festschreiben und dann andere Änderungen an dem Zweig abrufen, in dem Sie sich befanden.

Sie sagen, es macht keinen Sinn, aber Sie tun es nur, damit Sie sie nach dem Ziehen nach Belieben zusammenführen können. Offensichtlich besteht Ihre andere Wahl darin, sich auf Ihre Kopie des Zweigs festzulegen und dann den Pull durchzuführen. Die Vermutung ist, dass Sie das entweder nicht wollen (in diesem Fall bin ich verwirrt, dass Sie keinen Zweig wollen) oder dass Sie Angst vor Konflikten haben.

rauben
quelle
1
Ist nicht der richtige Befehl git stash apply? hier die docs.
Thomas8
1
Genau das, wonach ich gesucht habe, um vorübergehend zu verschiedenen Zweigen zu wechseln, etwas nachzuschlagen und zum gleichen Zustand des Zweigs zurückzukehren, an dem ich arbeite. Danke Rob!
Naishta
1
Ja, das ist der richtige Weg, dies zu tun. Ich schätze das Detail in der akzeptierten Antwort, aber das macht die Dinge schwieriger als nötig.
Michael Leonard
5
Wenn Sie den Stash nicht aufbewahren müssen, können Sie ihn verwenden. git stash popWenn er erfolgreich angewendet wird, wird der Stash von Ihrer Liste gelöscht.
Michael Leonard
1
Bessere Verwendung git stash pop, es sei denn, Sie beabsichtigen, ein Protokoll der Verstecke in Ihrer Repo-Geschichte zu führen
Damilola Olowookere
14

Wenn der neue Zweig Änderungen enthält, die sich vom aktuellen Zweig für diese bestimmte geänderte Datei unterscheiden, können Sie die Zweige erst wechseln, wenn die Änderung festgeschrieben oder gespeichert wurde. Wenn die geänderte Datei in beiden Zweigen gleich ist (dh in der festgeschriebenen Version dieser Datei), können Sie frei wechseln.

Beispiel:

$ echo 'hello world' > file.txt
$ git add file.txt
$ git commit -m "adding file.txt"

$ git checkout -b experiment
$ echo 'goodbye world' >> file.txt
$ git add file.txt
$ git commit -m "added text"
     # experiment now contains changes that master doesn't have
     # any future changes to this file will keep you from changing branches
     # until the changes are stashed or committed

$ echo "and we're back" >> file.txt  # making additional changes
$ git checkout master
error: Your local changes to the following files would be overwritten by checkout:
    file.txt
Please, commit your changes or stash them before you can switch branches.
Aborting

Dies gilt sowohl für nicht verfolgte als auch für verfolgte Dateien. Hier ist ein Beispiel für eine nicht verfolgte Datei.

Beispiel:

$ git checkout -b experimental  # creates new branch 'experimental'
$ echo 'hello world' > file.txt
$ git add file.txt
$ git commit -m "added file.txt"

$ git checkout master # master does not have file.txt
$ echo 'goodbye world' > file.txt
$ git checkout experimental
error: The following untracked working tree files would be overwritten by checkout:
    file.txt
Please move or remove them before you can switch branches.
Aborting

Ein gutes Beispiel dafür, warum Sie beim Vornehmen von Änderungen zwischen Zweigen wechseln möchten, wäre, wenn Sie einige Experimente mit dem Master durchführen, diese festschreiben, aber noch nicht beherrschen möchten ...

$ echo 'experimental change' >> file.txt # change to existing tracked file
   # I want to save these, but not on master

$ git checkout -b experiment
M       file.txt
Switched to branch 'experiment'
$ git add file.txt
$ git commit -m "possible modification for file.txt"
Gordolio
quelle
Eigentlich verstehe ich es immer noch nicht ganz. In Ihrem ersten Beispiel heißt es, nachdem Sie "und wir sind zurück" hinzugefügt haben, dass die lokale Änderung überschrieben wird. Welche lokale Änderung genau? "und wir sind zurück"? Warum trägt git diese Änderung nicht einfach zum Master, so dass die Datei im Master "Hallo Welt" und "und wir sind zurück" enthält
Xufeng
Im ersten Beispiel hat der Meister nur "Hallo Welt" festgelegt. Experiment hat "Hallo Welt \ ngoodbye Welt" begangen. Damit der Zweigwechsel stattfinden kann, muss file.txt geändert werden. Das Problem ist, dass es nicht festgeschriebene Änderungen gibt: "Hallo Welt \ ngoodbye Welt \ nund wir sind zurück".
Gordolio
1

Die richtige Antwort ist

git checkout -m origin/master

Es führt Änderungen aus dem Ursprungshauptzweig mit Ihren lokalen, auch nicht festgeschriebenen Änderungen zusammen.

JD1731
quelle
0

Falls Sie nicht möchten, dass diese Änderungen überhaupt übernommen werden, tun Sie dies git reset --hard.

Als nächstes können Sie zum gewünschten Zweig auschecken, aber denken Sie daran, dass nicht festgeschriebene Änderungen verloren gehen.

Kacpero
quelle