Uwagi wstępne
Obserwacja polega na tym, że po rozpoczęciu pracy branch1
(zapominając lub nie wiedząc, że dobrze byłoby branch2
najpierw 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 stash
do zapisania; jest to jedna z rzeczy, do których jest przeznaczona. Zauważ, że git stash save
lub git stash push
faktycznie 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 apply
po zmianie.
Pasek boczny: git stash save
to stara składnia; git stash push
został wprowadzony w Git w wersji 2.13, aby naprawić niektóre problemy z argumentami git stash
i 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 stash
lub git commit
; lub, jeśli zmiany są łatwe do odtworzenia, użyj, git checkout -f
aby je wymusić. Ta odpowiedź dotyczy tego, kiedy Git pozwoli ci, git checkout branch2
mimo ż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 add
s, git rm
s i takimi - załóżmy, że jesteś włączony branch1
. A git checkout branch2
musiałby to zrobić:
- Dla każdego pliku, który jest w,
branch1
a nie w branch2
, 1 usuń ten plik.
- Dla każdego pliku, który jest w
branch2
a nie w branch1
utworzyć ten plik (z odpowiednią zawartością).
- Jeśli dla każdego pliku w obu gałęziach
branch2
jest 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,
branch2
jest „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-point
Moż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
branch1
git rev-parse branch1:P
branch1
P
git rev-parse
branch-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ę branch1
i 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 inboth
zgodny z plikiem w branch2
, mimo że jesteśmy włączeni branch1
. Ta zmiana nie jest wprowadzana dla zatwierdzenia, co git status --short
pokazano 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 M
oznaczają tutaj: plik przemieszczany różni się od HEAD
pliku, 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 checkout
nie zezwoli na kasę:
$ git checkout branch2
error: Your local changes ...
Ustawmy branch2
wersję 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 checkout
straci tę kopię i git checkout
zostanie 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 add
plik, 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 add
do indeksu. Istnieją więc trzy miejsca dla każdego pliku: bieżący zatwierdzenie, indeks i drzewo robocze.
Po uruchomieniu git checkout branch2
Git pod pokrywami porównuje zatwierdzenie końcówki z tym, branch2
co 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.txt
to 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 checkout
dokumentacji nie w sposób, w jaki można się spodziewać, ale w git read-tree
dokumentacji, w części zatytułowanej „Scalanie dwóch drzew” .)
git checkout -m
funkcja, która łączy twoje środowisko pracy i indeksuje zmiany w nowej kasie.Masz dwie możliwości: ukryć swoje zmiany:
a później, aby je odzyskać:
lub umieść zmiany w gałęzi, aby uzyskać gałąź zdalną, a następnie scalić z nią zmiany. To jedna z największych zalet git: możesz utworzyć gałąź, zatwierdzić ją, a następnie pobrać inne zmiany w gałęzi, w której byłeś.
Mówisz, że to nie ma żadnego sensu, ale robisz to tylko po to, abyś mógł je dowolnie połączyć po pociągnięciu. Oczywiście innym wyborem jest zatwierdzenie kopii oddziału, a następnie wykonanie ściągnięcia. Zakłada się, że albo nie chcesz tego zrobić (w tym przypadku jestem zaskoczony, że nie chcesz oddziału), albo boisz się konfliktów.
źródło
git stash apply
? tutaj doktorzy.git stash pop
a jeśli z powodzeniem zastosuje, usunie skrytkę z listy.git stash pop
, chyba że masz zamiar prowadzić dziennik zapasów w historii repoJeśli nowa gałąź zawiera zmiany, które różnią się od bieżącej gałęzi dla tego konkretnego zmienionego pliku, nie pozwoli ona na zmianę gałęzi, dopóki zmiana nie zostanie zatwierdzona lub zablokowana. Jeśli zmieniony plik jest taki sam w obu gałęziach (czyli zatwierdzonej wersji tego pliku), możesz swobodnie się przełączać.
Przykład:
Dotyczy to zarówno nieśledzonych plików, jak i plików śledzonych. Oto przykład nieśledzonego pliku.
Przykład:
Dobrym przykładem tego, dlaczego CHCESZ przemieszczać się między gałęziami, wprowadzając zmiany, byłoby, gdybyś przeprowadzał pewne eksperymenty na masterie, chciałem je zatwierdzić, ale jeszcze nie opanować ...
źródło
Poprawna odpowiedź to
git checkout -m origin/master
Łączy zmiany z głównej gałęzi pochodzenia z lokalnymi, nawet niezatwierdzonymi zmianami.
źródło
Jeśli nie chcesz, aby te zmiany zostały w ogóle zatwierdzone, zrób to
git reset --hard
.Następnie możesz przejść do wybranej gałęzi, ale pamiętaj, że niezatwierdzone zmiany zostaną utracone.
źródło