Uwagi wstępne
Obserwacja polega na tym, że po rozpoczęciu pracy branch1(zapominając lub nie wiedząc, że dobrze byłoby branch2najpierw przejść do innej gałęzi ), uruchomisz:
git checkout branch2
Czasami Git mówi „OK, jesteś teraz na branch2!” Czasami Git mówi: „Nie mogę tego zrobić, straciłbym niektóre z twoich zmian”.
Jeśli Git nie pozwoli ci tego zrobić, musisz zatwierdzić zmiany, aby zapisać je gdzieś na stałe. Możesz użyć ich git stashdo zapisania; jest to jedna z rzeczy, do których jest przeznaczona. Zauważ, że git stash savelub git stash pushfaktycznie oznacza „Zatwierdź wszystkie zmiany, ale w żadnym oddziale w ogóle, a następnie usuń je z miejsca, w którym teraz jestem”. Umożliwia to zmianę: teraz nie masz żadnych zmian w toku. Możesz je potem git stash applypo zmianie.
Pasek boczny: git stash saveto stara składnia; git stash pushzostał wprowadzony w Git w wersji 2.13, aby naprawić niektóre problemy z argumentami git stashi pozwolić na nowe opcje. Oba robią to samo, gdy są używane w podstawowy sposób.
Możesz przestać czytać tutaj, jeśli chcesz!
Jeśli Git nie pozwoli ci się zmienić, masz już lekarstwo: użyj git stashlub git commit; lub, jeśli zmiany są łatwe do odtworzenia, użyj, git checkout -faby je wymusić. Ta odpowiedź dotyczy tego, kiedy Git pozwoli ci, git checkout branch2mimo że zacząłeś wprowadzać zmiany. Dlaczego to działa czasami , a nie innym razem?
Zasada tutaj jest prosta w jeden sposób, a skomplikowana / trudna do wyjaśnienia w inny sposób:
Możesz przełączać gałęzie z niezatwierdzonymi zmianami w drzewie roboczym tylko wtedy, gdy wspomniane przełączanie nie wymaga usuwania tych zmian.
To znaczy - i proszę zauważyć, że jest to nadal uproszczone; są pewne wyjątkowo trudne skrzynie narożne ze scenami git adds, git rms i takimi - załóżmy, że jesteś włączony branch1. A git checkout branch2musiałby to zrobić:
- Dla każdego pliku, który jest w,
branch1a nie w branch2, 1 usuń ten plik.
- Dla każdego pliku, który jest w
branch2a nie w branch1utworzyć ten plik (z odpowiednią zawartością).
- Jeśli dla każdego pliku w obu gałęziach
branch2jest inna wersja , zaktualizuj działającą wersję drzewa.
Każdy z tych kroków może zablokować coś w twoim drzewie roboczym:
- Usunięcie pliku jest „bezpieczne”, jeśli wersja w drzewie roboczym jest taka sama, jak wersja zatwierdzona w
branch1; jest to „niebezpieczne”, jeśli dokonałeś zmian.
- Utworzenie pliku tak, jak wygląda,
branch2jest „bezpieczne”, jeśli nie istnieje teraz. 2 Jest „niebezpieczny”, jeśli już istnieje, ale ma „niewłaściwą” treść.
- I oczywiście zastąpienie wersji drzewa roboczego inną wersją jest „bezpieczne”, jeśli wersja drzewa roboczego jest już zobowiązana
branch1.
Utworzenie nowej gałęzi ( git checkout -b newbranch) jest zawsze uważane za „bezpieczne”: żadne pliki nie będą dodawane, usuwane ani zmieniane w drzewie roboczym w ramach tego procesu, a także indeks / obszar pomostowy pozostaje nietknięty. (Zastrzeżenie: jest bezpieczne podczas tworzenia nowej gałęzi bez zmiany punktu początkowego nowej gałęzi; ale jeśli dodasz inny argument, np. git checkout -b newbranch different-start-pointMoże to wymagać zmiany rzeczy, aby przejść do different-start-point. Git zastosuje reguły bezpieczeństwa przy kasie jak zwykle .)
1 Wymaga to zdefiniowania, co oznacza plik znajdujący się w gałęzi, co z kolei wymaga prawidłowego zdefiniowania gałęzi słowa . (Patrz również co dokładnie rozumiemy przez „oddział”? ) Oto, co mam na myśli to commit do którego postanawia branch-name: plik, którego ścieżka jest w razie produkuje hash. Ten plik nie jest w jeśli pojawi się komunikat o błędzie zamiast. Istnienie ścieżki w twoim indeksie lub drzewie roboczym nie ma znaczenia, gdy odpowiadasz na to konkretne pytanie. Zatem sekretem jest tutaj zbadanie wyniku każdego z nichP branch1git rev-parse branch1:Pbranch1Pgit rev-parsebranch-name:path. To się nie udaje, ponieważ plik znajduje się „w” co najwyżej jednej gałęzi lub daje nam dwa identyfikatory skrótu. Jeśli dwa identyfikatory skrótu są takie same , plik jest taki sam w obu gałęziach. Zmiana nie jest wymagana. Jeśli identyfikatory skrótów różnią się, plik jest inny w dwóch gałęziach i należy go zmienić, aby zmienić gałęzie.
Kluczowym pojęciem jest to, że pliki w commits są zamrażane na zawsze. Pliki, które będziesz edytować, oczywiście nie są zamrożone. Przynajmniej na początku patrzymy tylko na niedopasowania między dwoma zamrożonymi zobowiązaniami. Niestety, my - lub Git - mamy również do czynienia z plikami, które nie znajdują się w zatwierdzeniu, z którego chcesz się przełączyć, i są w zatwierdzeniu, na które chcesz się przełączyć. Prowadzi to do pozostałych komplikacji, ponieważ pliki mogą również istnieć w indeksie i / lub w drzewie roboczym, bez konieczności istnienia tych dwóch zamrożonych zatwierdzeń, z którymi pracujemy.
2 Może być uważany za „trochę bezpieczny”, jeśli już istnieje z „właściwą treścią”, tak że Git nie musi go jednak tworzyć. Przywołuję przynajmniej niektóre wersje Gita, które na to pozwalają, ale testowanie właśnie teraz pokazuje, że jest to uważane za „niebezpieczne” w Git 1.8.5.4. Ten sam argument dotyczyłby zmodyfikowanego pliku, który akurat jest modyfikowany, aby pasował do gałęzi „przełącz się na”. Znowu 1.8.5.4 mówi tylko, że „zostanie zastąpiony”. Zobacz także koniec uwag technicznych: moja pamięć może być wadliwa, ponieważ nie sądzę, aby reguły drzewa odczytu zmieniły się, odkąd zacząłem używać Gita w wersji 1.5.
Czy ma znaczenie, czy zmiany są wprowadzane etapowo, czy nie?
Tak, pod pewnymi względami. W szczególności możesz wprowadzić zmiany, a następnie „odinstalować” plik drzewa roboczego. Oto plik w dwóch oddziałów, to różnią się branch1i 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
W tym momencie działający plik drzewa jest inbothzgodny z plikiem w branch2, mimo że jesteśmy włączeni branch1. Ta zmiana nie jest wprowadzana dla zatwierdzenia, co git status --shortpokazano tutaj:
$ git status --short
M inboth
Spacja następnie M oznacza „zmodyfikowany, ale nie przemieszczany” (a ściślej mówiąc, kopia drzewa roboczego różni się od kopii przemieszczanej / indeksowanej).
$ git checkout branch2
error: Your local changes ...
OK, przygotujmy teraz kopię drzewa roboczego, w której wiemy, że pasuje również do kopii branch2.
$ git add inboth
$ git status --short
M inboth
$ git checkout branch2
Switched to branch 'branch2'
Tutaj zarówno zainscenizowane, jak i robocze kopie pasowały do tego, co było w branch2środku, więc kasa była dozwolona.
Spróbujmy kolejnego kroku:
$ git checkout branch1
Switched to branch 'branch1'
$ cat inboth
this file is in both branches
Wprowadzona przeze mnie zmiana została teraz utracona z obszaru przeciwności (ponieważ kasa zapisuje przez obszar przeciwności). To trochę narożna sprawa. Zmiana nie ma, ale fakt, że miałem go wystawił, to już nie ma.
Zróbmy trzeci wariant pliku, inny niż kopia gałęzi, a następnie ustaw kopię roboczą, aby pasowała do bieżącej wersji gałęzi:
$ echo 'staged version different from all' > inboth
$ git add inboth
$ git show branch1:inboth > inboth
$ git status --short
MM inboth
Te dwa Moznaczają tutaj: plik przemieszczany różni się od HEADpliku, a plik drzewa roboczego różni się od pliku przemieszczanego. Wersja drzewa roboczego jest zgodna z wersją branch1(aka HEAD):
$ git diff HEAD
$
Ale git checkoutnie zezwoli na kasę:
$ git checkout branch2
error: Your local changes ...
Ustawmy branch2wersję jako działającą:
$ 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 ...
Chociaż bieżąca kopia robocza jest zgodna z tą w branch2, plik przemieszczany nie, więc a git checkoutstraci tę kopię i git checkoutzostanie odrzucony.
Uwagi techniczne - tylko dla niesamowicie ciekawskich :-)
Podstawowym mechanizmem implementacji tego wszystkiego jest indeks Git . Indeks, zwany także „obszarem przejściowym”, jest miejscem, w którym budujesz następny zatwierdzenie: zaczyna się od bieżącego zatwierdzenia, tj. Niezależnie od tego, co wyewidencjonowałeś, a następnie za każdym razem, gdy tworzysz git addplik, zastępujesz wersję indeksu z tym, co masz w swoim drzewie roboczym.
Pamiętaj, że drzewo robocze to miejsce, w którym pracujesz nad plikami. Tutaj mają swoją normalną formę, a nie jakąś specjalną, użyteczną tylko dla Gita formę, taką jak w commits i indeksie. Tak więc wyodrębniasz plik z zatwierdzenia, poprzez indeks, a następnie do drzewa roboczego. Po zmianie, ty git adddo indeksu. Istnieją więc trzy miejsca dla każdego pliku: bieżący zatwierdzenie, indeks i drzewo robocze.
Po uruchomieniu git checkout branch2Git pod pokrywami porównuje zatwierdzenie końcówki z tym, branch2co jest teraz w bieżącym zatwierdzeniu i indeksie. Każdy plik, który pasuje do tego, co jest teraz, Git może zostawić w spokoju. Wszystko jest nietknięte. Każdy plik, który jest taki sam w obu zatwierdzeniach , Git może również zostawić w spokoju - i to one umożliwiają zmianę gałęzi.
Znaczna część Git, w tym przełączanie zmian , jest stosunkowo szybka z powodu tego indeksu. W indeksie nie znajduje się sam plik, ale skrót każdego pliku . Kopia samego pliku jest przechowywana w repozytorium jako to, co Git nazywa obiektem obiektu blob . Jest to podobne do sposobu przechowywania plików również w commits: commits tak naprawdę nie zawierają plików , po prostu prowadzą Git do identyfikatora skrótu każdego pliku. Dzięki temu Git może porównywać identyfikatory skrótów - obecnie ciągi o długości 160 bitów - aby zdecydować, czy zatwierdzenia X i Y mają ten sam plik, czy nie. Następnie może porównać te identyfikatory skrótu z identyfikatorem skrótu w indeksie.
To właśnie prowadzi do wszystkich powyższych przypadków narożników nieparzystych. Mamy zatwierdzenia X i Y, które oba mają plik path/to/name.txt, i mamy wpis indeksu dla path/to/name.txt. Może wszystkie trzy skróty pasują do siebie. Może dwa z nich pasują, a jeden nie. Może wszystkie trzy są różne. Możemy też mieć another/file.txtto tylko w X lub tylko w Y i jest teraz w indeksie lub nie. Każdy z tych różnych przypadków wymaga odrębnego rozpatrzenia: czy Git musi skopiować plik z zatwierdzenia do indeksu lub usunąć go z indeksu, aby przełączyć się z X na Y ? Jeśli tak, to również musiskopiuj plik do drzewa roboczego lub usuń go z drzewa roboczego. W takim przypadku wersje indeksu i drzewa roboczego lepiej pasują co najmniej do jednej z zatwierdzonych wersji; w przeciwnym razie Git zablokuje niektóre dane.
(Kompletne reguły tego wszystkiego są opisane w git checkoutdokumentacji nie w sposób, w jaki można się spodziewać, ale w git read-treedokumentacji, w części zatytułowanej „Scalanie dwóch drzew” .)
git checkout -mfunkcja, która łączy twoje środowisko pracy i indeksuje zmiany w nowej kasie.