W prostym języku angielskim, co robi „git reset”?

674

Widziałem ciekawe posty wyjaśniające subtelności git reset.

Niestety, im więcej o tym czytam, tym bardziej wydaje się, że nie rozumiem go w pełni. Pochodzę z SVN, a Git to zupełnie nowy paradygmat. Łatwo dostałem rtęć, ale Git jest o wiele bardziej techniczny.

Myślę, że git resetjest blisko hg revert, ale wydaje się, że istnieją różnice.

Co dokładnie robi git reset? Proszę podać szczegółowe wyjaśnienia dotyczące:

  • opcje --hard, --softa --merge;
  • dziwna notacja, której używasz, na HEADprzykład HEAD^i HEAD~1;
  • konkretne przypadki użycia i przepływy pracy;
  • konsekwencje dla kopii roboczej HEADoraz globalnego poziomu stresu.
e-satis
źródło
17
Myślę, że Visual Git Reference daje dobry wgląd w to, co dzieje się podczas używania typowych poleceń git.

Odpowiedzi:

992

Ogólnie rzecz biorąc, git resetfunkcja polega na pobraniu bieżącej gałęzi i zresetowaniu jej, aby wskazywała gdzieś indziej, i ewentualnie zabraniu ze sobą indeksu i drzewa roboczego. Mówiąc konkretniej, jeśli twoja główna gałąź (obecnie wypisana) wygląda następująco:

- A - B - C (HEAD, master)

i zdajesz sobie sprawę, że chcesz, aby mistrz wskazywał na B, a nie na C, użyjesz git reset Bgo tam:

- A - B (HEAD, master)      # - C is still here, but there's no branch pointing to it anymore

Dygresja: różni się od kasy. Gdybyś biegł git checkout B, dostałbyś to:

- A - B (HEAD) - C (master)

Skończyło się na odłączeniu HEAD. HEAD, drzewo pracy, indeksuj wszystkie pasujące B, ale gałąź master pozostała w miejscu C. Jeśli dokonasz nowego zatwierdzenia Dw tym momencie, otrzymasz to, co prawdopodobnie nie jest tym, czego chcesz:

- A - B - C (master)
       \
        D (HEAD)

Pamiętaj, że reset nie dokonuje zatwierdzeń, po prostu aktualizuje gałąź (która jest wskaźnikiem zatwierdzenia), aby wskazać inne zatwierdzenie. Reszta to tylko szczegóły tego, co dzieje się z twoim indeksem i drzewem pracy.

Przypadków użycia

Omawiam wiele głównych przypadków użycia w git resetramach moich opisów różnych opcji w następnej sekcji. Można go naprawdę używać do wielu różnych rzeczy; wspólny wątek polega na tym, że wszystkie wymagają resetowania gałęzi, indeksu i / lub drzewa roboczego, aby wskazać / dopasować dane zatwierdzenie.

Rzeczy, na które należy uważać

  • --hardmoże spowodować, że naprawdę stracisz pracę. Zmienia twoje drzewo pracy.

  • git reset [options] commitmoże powodować (poniekąd) utratę zatwierdzeń. W powyższym przykładzie zabawki straciliśmy zatwierdzenie C. Nadal jest w repozytorium i można go znaleźć, patrząc na git reflog show HEADlub git reflog show master, ale w rzeczywistości nie jest już dostępny z żadnej gałęzi.

  • Git trwale usuwa takie zatwierdzenia po 30 dniach, ale do tego czasu możesz odzyskać C, ponownie wskazując na niego gałąź ( git checkout C; git branch <new branch name>).

Argumenty

Parafrazując stronę podręcznika, najczęstszym zastosowaniem jest forma git reset [<commit>] [paths...], która zresetuje podane ścieżki do ich stanu z podanego zatwierdzenia. Jeśli ścieżki nie zostaną podane, całe drzewo zostanie zresetowane, a jeśli zatwierdzenie nie zostanie podane, zostanie uznane za HEAD (bieżące zatwierdzenie). Jest to powszechny wzór w poleceniach git (np. Checkout, diff, log, choć dokładna semantyka jest różna), więc nie powinno być zbyt zaskakujące.

Na przykład git reset other-branch path/to/fooresetuje wszystko w ścieżce / do / foo do stanu w innej gałęzi, git reset -- .resetuje bieżący katalog do stanu w HEAD, a prosty git resetresetuje wszystko do stanu w HEAD.

Główne drzewo pracy i opcje indeksu

Istnieją cztery główne opcje kontrolowania tego, co dzieje się z drzewem pracy i indeksem podczas resetowania.

Pamiętaj, że indeks jest „obszarem przejściowym” gita - tam gdzie idą rzeczy, kiedy mówisz git addw ramach przygotowań do zatwierdzenia.

  • --harddopasowuje wszystko do zatwierdzonego resetu. Prawdopodobnie jest to najłatwiejszy do zrozumienia. Wszystkie lokalne zmiany zostaną zablokowane. Jednym z podstawowych zastosowań jest zdmuchnięcie twojej pracy, ale nie zmiana zatwierdzeń: git reset --hardoznacza git reset --hard HEAD, tzn. Nie zmieniaj gałęzi, ale pozbądź się wszystkich lokalnych zmian. Drugim jest po prostu przenoszenie gałęzi z jednego miejsca do drugiego i utrzymywanie synchronizacji drzewa indeksu / pracy. Jest to ten, który może naprawdę spowodować utratę pracy, ponieważ modyfikuje twoje drzewo pracy. Bądź bardzo pewny, że chcesz odrzucić pracę lokalną, zanim ją uruchomisz reset --hard.

  • --mixedjest wartością domyślną, tj . git resetoznacza git reset --mixed. Resetuje indeks, ale nie drzewo robocze. Oznacza to, że wszystkie twoje pliki są nienaruszone, ale wszelkie różnice między oryginalnym zatwierdzeniem a tym, którego zresetowałeś, pojawią się jako lokalne modyfikacje (lub nieśledzone pliki) ze statusem git. Użyj tego, gdy zdasz sobie sprawę, że popełniłeś złe błędy, ale chcesz zachować całą pracę, którą wykonałeś, aby to naprawić i ponownie uruchomić. Aby zatwierdzić, musisz ponownie dodać pliki do indeksu ( git add ...).

  • --softnie dotyka indeksu ani drzewa pracy. Wszystkie twoje pliki są nienaruszone jak przy --mixed, ale wszystkie zmiany pokazują się jak changes to be committedprzy statusie git (tj. Sprawdzone w przygotowaniu do zatwierdzenia). Użyj tego, gdy zdasz sobie sprawę, że popełniłeś złe błędy, ale praca jest dobra - wszystko, co musisz zrobić, to ponownie ją uruchomić. Indeks jest nietknięty, więc możesz zatwierdzić natychmiast, jeśli chcesz - wynikowe zatwierdzenie będzie miało taką samą treść, jak w miejscu, w którym byłeś przed zresetowaniem.

  • --mergezostał dodany niedawno i ma na celu pomóc w przerwaniu nieudanego scalenia. Jest to konieczne, ponieważ git mergefaktycznie pozwoli ci podjąć próbę scalenia z brudnym drzewem roboczym (jednym z lokalnymi modyfikacjami), o ile modyfikacje te znajdują się w plikach, na które nie ma wpływu scalanie. git reset --mergeresetuje indeks (jak --mixed- wszystkie zmiany są wyświetlane jako modyfikacje lokalne) i resetuje pliki, na które wpływa scalanie, ale pozostawia inne w spokoju. Miejmy nadzieję, że przywróci to wszystko tak, jak było przed złym połączeniem. Zazwyczaj używasz go jako git reset --merge(znaczącego git reset --merge HEAD), ponieważ chcesz tylko zresetować scalenie, a nie przesunąć gałąź. ( HEADnie został jeszcze zaktualizowany, ponieważ scalenie nie powiodło się)

    Aby być bardziej konkretnym, załóżmy, że zmodyfikowałeś pliki A i B i próbujesz scalić w gałęzi, która zmodyfikowała pliki C i D. Scalanie z jakiegoś powodu kończy się niepowodzeniem i decydujesz się go przerwać. Ty używasz git reset --merge. Powoduje, że C i D wracają do tego, jak były HEAD, ale pozostawia twoje modyfikacje w A i B w spokoju, ponieważ nie były one częścią próby scalenia.

Chcieć wiedzieć więcej?

Sądzę, że man git resetjest to całkiem dobre - być może potrzebujesz trochę sposobu, w jaki działa git, aby naprawdę się zagłębili. W szczególności, jeśli poświęcisz trochę czasu na ich uważne przeczytanie, bardzo pomocne są te tabele przedstawiające stany plików w indeksie i drzewie pracy dla wszystkich różnych opcji i przypadków. (Ale tak, są bardzo gęste - przekazują bardzo dużo powyższych informacji w bardzo zwięzłej formie).

Dziwna notacja

„Dziwny zapis” ( HEAD^i HEAD~1), o którym wspominasz, jest po prostu skrótem do określania zatwierdzeń, bez konieczności używania nazwy skrótu takiej jak 3ebe3f6. Jest to w pełni udokumentowane w sekcji „określanie wersji” strony podręcznika dla git-rev-pars, z dużą ilością przykładów i powiązanej składni. Karetka i tylda faktycznie oznaczają różne rzeczy :

  • HEAD~jest skrótem od HEAD~1i oznacza pierwszego rodzica zatwierdzenia. HEAD~2oznacza pierwszego rodzica pierwszego zatwierdzenia. Pomyśl o tym, HEAD~njak „n zobowiązuje się przed HEAD” lub „przodek HEAD n-tej generacji”.
  • HEAD^(lub HEAD^1) oznacza również pierwszego rodzica zatwierdzenia. HEAD^2oznacza drugiego rodzica zatwierdzenia . Pamiętaj, że normalne zatwierdzenie scalania ma dwoje rodziców - pierwszym rodzicem jest zatwierdzone scalenie, a drugim rodzicem jest scalone zatwierdzenie. Zasadniczo połączenia mogą mieć dowolnie wielu rodziców (połączenia ośmiornic).
  • Te ^i ~operatorzy mogą być połączone razem, tak jak w HEAD~3^2, drugi macierzysty przodka trzeciej generacji HEAD, HEAD^^2, drugi macierzysty pierwszego rodzica HEADlub nawet HEAD^^^, co jest równoważne HEAD~3.

karetka i tylda

Cascabel
źródło
„użyjesz git reset, aby go tam przenieść”. dlaczego nie używasz do tego git checkout?
e-satis
5
@ e-satis: git checkout przeniesie HEAD, ale zostaw gałąź tam, gdzie była. Dzieje się tak, gdy chcesz przenieść gałąź.
Cascabel
Więc jeśli dobrze rozumiem, reset B zrobiłby: - ​​A - B - C - B (master), a kasa B zrobiłby - A - B (master)?
e-satis
32
Dokumenty są dobre, mimo że ich odczytanie trwa wieczność i są bardzo gęste, a sprawdzenie, czy mówią, że działa tak, jak wiesz, już działa. Nie wygląda na to, że doktorzy są dla mnie dobrzy ...
Kirby,
4
Bardzo proste i zrozumiałe wyjaśnienie znajduje się w tej odpowiedzi SO: stackoverflow.com/questions/3528245/whats-the-difference-between-git-reset-mixed-soft-and-hard
Nitin Bansal
80

Pamiętaj, że gitmasz w sobie:

  • HEADwskaźnik , który mówi, co popełnienia pracujesz na
  • drzewo pracy , który reprezentuje stan plików w systemie
  • obszar staging (zwany także indeks ), który „etapy” zmienia się tak, że mogą później być popełnione wspólnie

Proszę podać szczegółowe wyjaśnienia dotyczące:

--hard, --softI --merge;

W porządku rosnącym niebezpieczeństw:

  • --softporusza się, HEADale nie dotyka obszaru przeciwności ani drzewa roboczego.
  • --mixedprzesuwa HEADi aktualizuje obszar przemieszczania, ale nie działające drzewo.
  • --mergeprzesuwa się HEAD, resetuje obszar przemieszczania i próbuje przenieść wszystkie zmiany z drzewa roboczego do nowego drzewa roboczego.
  • --hardprzesuwa HEAD i dostosowuje obszar postoju i drzewo robocze do nowego HEAD, wyrzucając wszystko.

konkretne przypadki użycia i przepływy pracy;

  • Użyj, --softgdy chcesz przejść do innego zatwierdzenia i załatać rzeczy bez „utraty swojego miejsca”. Bardzo rzadko potrzebujesz tego.

-

# git reset --soft example
touch foo                            // Add a file, make some changes.
git add foo                          // 
git commit -m "bad commit message"   // Commit... D'oh, that was a mistake!
git reset --soft HEAD^               // Go back one commit and fix things.
git commit -m "good commit"          // There, now it's right.

-

  • Użyj --mixed(która jest domyślna), jeśli chcesz zobaczyć, jak wyglądają inne zatwierdzenia, ale nie chcesz stracić żadnych zmian, które już masz.

  • Użyj, --mergegdy chcesz przenieść się w nowe miejsce, ale uwzględnij zmiany, które już wprowadziłeś, do działającego drzewa.

  • Użyj, --hardaby wymazać wszystko i rozpocząć nową tablicę przy nowym zatwierdzeniu.

John Feminella
źródło
2
To nie jest zamierzony przypadek użycia dla reset --merge. Nie wykonuje łączenia trójstronnego. To tak naprawdę tylko do resetowania konfliktów scalania, jak opisano w dokumentacji. Będziesz chciał wykorzystać checkout --mergeto, o czym mówisz. Jeśli chcesz również przenieść gałąź, myślę, że jedynym sposobem jest kontynuacja kasy / resetowania, aby przeciągnąć ją dalej.
Cascabel
@Jefromi »Tak, nie sformułowałem tego zbyt dobrze. Przez „nowe miejsce” miałem na myśli „świeże miejsce, w którym nie ma konfliktu scalenia”.
John Feminella
1
O, rozumiem. Myślę, że ważną rzeczą jest to, że jeśli naprawdę nie wiesz, co robisz, prawdopodobnie nigdy nie chcesz używać reset --mergez żadnym innym celem (domyślnym) HEAD, ponieważ w przypadkach innych niż przerwanie konfliktu scalenia, zostanie ono odrzucone informacje, które w innym przypadku można zapisać.
Cascabel
2
Uznałem tę odpowiedź za najprostszą i najbardziej pomocną
Jazzepi,
Dodaj informacje o tych poleceniach: git reseti git reset -- ..
Flimm,
35

Post Wyczyść Demystified w blogu Pro Git daje bardzo nie myślenia wyjaśnienie git reseti git checkout.

Po wszystkich pomocnych dyskusjach u góry tego postu autor ogranicza reguły do ​​następujących trzech prostych kroków:

To jest w zasadzie to. resetKomenda nadpisuje te trzy drzewa w kolejności określonej, zatrzymując się, gdy powiesz jej.

  1. Przesuń dowolną gałąź HEAD do punktu (zatrzymaj, jeśli --soft)
  2. NASTĘPNIE spraw, aby Indeks wyglądał tak (zatrzymaj się tutaj, chyba że --hard)
  3. Następnie spraw, aby katalog roboczy wyglądał tak

Są też --mergei --keepopcje, ale wolałbym na razie uprościć sprawę - tak będzie w przypadku innego artykułu.

Daniel Hershcovich
źródło
25

Kiedy coś popełniasz, musisz najpierw wprowadzić zmiany (dodać do indeksu). Oznacza to, że musisz dodać wszystkie pliki, które chcesz dołączyć do tego zatwierdzenia, zanim git uzna je za część zatwierdzenia. Najpierw spójrzmy na obraz repozytorium git: wprowadź opis zdjęcia tutaj

więc teraz jest to proste. Musimy pracować w katalogu roboczym, tworząc pliki, katalogi i tak dalej. Te zmiany nie są śledzone. Aby je śledzić, musimy dodać je do indeksu git za pomocą polecenia git add . Po dodaniu do indeksu git. Możemy teraz zatwierdzić te zmiany, jeśli chcemy przekazać je do repozytorium git.

Ale nagle przekonaliśmy się podczas zatwierdzania, że ​​mamy jeden dodatkowy plik, który dodaliśmy w indeksie, nie jest wymagany do wypchnięcia repozytorium git. Oznacza to, że nie chcemy tego pliku w indeksie. Teraz pytanie brzmi: jak usunąć ten plik z indeksu git, skoro użyliśmy git add, aby umieścić je w indeksie, logiczne byłoby użycie git rm ? Źle! git rm po prostu usunie plik i doda usunięcie do indeksu. Co więc teraz zrobić:

Posługiwać się:-

git reset

Czyści indeks, pozostawia nietknięty katalog roboczy. (po prostu wszystko inscenizuje).

Może być używany z wieloma opcjami. Istnieją trzy główne opcje do użycia z git reset: --hard, --soft i --mixed . Mają one wpływ na to, co zostanie zresetowane oprócz wskaźnika HEAD po zresetowaniu.

Po pierwsze --hard resetuje wszystko. Twój bieżący katalog byłby dokładnie taki, jak gdybyś śledził cały ten oddział. Katalog roboczy i indeks są zmieniane na to zatwierdzenie. To jest wersja, której najczęściej używam. git reset --hard jest czymś w rodzaju svn revert .

Następnie całkowite przeciwieństwo —miękkie ” nie resetuje działającego drzewa ani indeksu. Porusza tylko wskaźnikiem HEAD. To pozostawia twój aktualny stan z wszelkimi zmianami innymi niż zatwierdzenie, do którego się przełączasz, na miejscu w twoim katalogu i „przemieszczane” do zatwierdzenia. Jeśli dokonujesz zatwierdzenia lokalnie, ale nie wypchnąłeś go na serwer git, możesz zresetować do poprzedniego zatwierdzenia i ponownie uruchomić z dobrym komunikatem zatwierdzenia.

Wreszcie --mixed resetuje indeks, ale nie działające drzewo. Tak więc wszystkie zmiany są nadal obecne, ale są „nieetapowane” i trzeba by je dodać git add'ed lub git commit -a . używamy tego czasami, jeśli popełniliśmy więcej niż zamierzaliśmy za pomocą git commit -a, możemy wycofać zatwierdzenie za pomocą git reset --mixed, dodać rzeczy, które chcemy zatwierdzić i po prostu je zatwierdzić.

Różnica między git revert i git reset : -


Krótko mówiąc, git reset to polecenie „napraw błędy niezatwierdzone”, a git revert to polecenie „napraw błąd popełniony” .

Oznacza to, że jeśli popełniliśmy błąd w jakiejś zmianie, zatwierdziliśmy go i wypchnęliśmy to samo do git repo, to git revert jest rozwiązaniem. A jeśli w przypadku zidentyfikowania tego samego błędu przed wypchnięciem / zatwierdzeniem, możemy użyć git reset, aby naprawić problem.

Mam nadzieję, że pomoże ci to pozbyć się zamieszania.

miłość
źródło
2
To jest ładna prosta angielska odpowiedź na pytanie OP.
Episodex
1
Mimo że mogłem przeoczyć to w twojej odpowiedzi. Co to jest git reset HEADdomyślnie? --hard, --softLub --mixed? Świetna odpowiedź btw.
giannis christofakis
1
Świetna odpowiedź, ale uczynię to jaśniejszym, git reset --hardco spowoduje utratę części danych. I jest punkt, który może być błędny (choć nie jestem w 100% pewien ... Wciąż się uczę!): Mówiąc o --mixedtobie, mówisz, że „używamy tego czasami, jeśli popełniliśmy więcej niż zamierzaliśmy za pomocą git commit -a”. Czy miałeś na myśli: „jeśli wystawiliśmy więcej niż zamierzaliśmy z git stage .”? Jeśli naprawdę to popełniłeś, myślę, że jest już za późno (jak powiedziałeś na końcu, git reset to polecenie „napraw błędy niezamierzone”)
Fabio mówi Przywróć Monikę
6

TL; DR

git resetresetuje Staging do ostatniego zatwierdzenia. Służy --hardrównież do resetowania plików w katalogu roboczym do ostatniego zatwierdzenia.

DŁUŻSZA WERSJA

Ale to oczywiście uproszczone, stąd wiele raczej pełnych odpowiedzi. Bardziej sensowne było dla mnie czytanie git resetw kontekście cofania zmian. Np. Zobacz to:

Jeśli git revert jest „bezpiecznym” sposobem na cofnięcie zmian, możesz myśleć o git reset jako niebezpiecznej metodzie. Kiedy cofasz za pomocą git reset (a do zatwierdzeń nie odwołuje się żaden ref ani reflog), nie ma możliwości odzyskania oryginalnej kopii - jest to trwałe cofnięcie. Korzystając z tego narzędzia, należy zachować ostrożność, ponieważ jest to jedno z niewielu poleceń Git, które może potencjalnie spowodować utratę pracy.

Od https://www.atlassian.com/git/tutorials/undoing-changes/git-reset

i to

Na poziomie zatwierdzania resetowanie jest sposobem przeniesienia wierzchołka gałęzi do innego zatwierdzenia. Można tego użyć do usunięcia zatwierdzeń z bieżącej gałęzi.

Od https://www.atlassian.com/git/tutorials/resetting-checking-out-and-reverting/commit-level-operations

Snowcrash
źródło
2

Należy pamiętać, że jest to uproszczone wyjaśnienie, które jest pierwszym krokiem do zrozumienia tej złożonej funkcjonalności.

Może być pomocny dla uczniów wizualnych, którzy chcą wizualizować, jak wygląda stan projektu po każdym z tych poleceń:


Dla tych, którzy używają Terminalu z włączonym kolorem (git config --global color.ui auto):

git reset --soft A a zobaczysz rzeczy B i C w kolorze zielonym (wyreżyserowane i gotowe do zatwierdzenia)

git reset --mixed A(lub git reset A), a zobaczysz rzeczy B i C na czerwono (nie zainscenizowane i gotowe do przygotowania (na zielono), a następnie popełnione)

git reset --hard A i nigdzie nie zobaczysz zmian B i C (będzie tak, jakby nigdy nie istniały)


Lub dla tych, którzy korzystają z programu GUI, takiego jak „Tower” lub „SourceTree”

git reset --soft A i zobaczysz rzeczy B i C w obszarze „pliki przemieszczane” gotowe do zatwierdzenia

git reset --mixed A(lub git reset A), a zobaczysz rzeczy B i C w obszarze „pliki niestacjonarne” gotowe do przeniesienia na etapowe, a następnie zatwierdzone

git reset --hard A i nigdzie nie zobaczysz zmian B i C (będzie tak, jakby nigdy nie istniały)

timhc22
źródło
1

Kasa wskazuje na konkretne zatwierdzenie.

Resetuj wskazuje gałąź na konkretny zatwierdzenie. (Gałąź jest wskaźnikiem zatwierdzenia.)

Nawiasem mówiąc, jeśli twoja głowa nie wskazuje na zatwierdzenie, na które wskazuje również gałąź, to masz odłączoną głowę. (okazało się błędne. Zobacz komentarze ...)

Ian Warburton
źródło
1
Nie po to, aby podrywać, ale (tak, to właściwie jest podrywanie, ale dodajmy to do końca) Twoje trzecie zdanie jest technicznie nieprawdziwe. Powiedzmy, że twój HEAD wskazuje na gałąź B, która z kolei wskazuje na zatwierdzenie abc123. Jeśli teraz kasujesz zatwierdzanie abc123, zarówno HEAD, jak i gałąź B wskazują na zatwierdzenie abc123, a HEAD jest odłączony. Podjęcie w tym momencie nie zaktualizuje pozycji gałęzi B. Mógłbyś powiedzieć „jeśli głowa nie wskazuje na gałąź, to masz odłączoną głowę”
RomainValeri
@RainainValeri Co zrobi zobowiązanie w takich okolicznościach?
Ian Warburton,
1
Zatwierdzanie tworzyłoby zatwierdzenia, do których nie odwołuje się żadna gałąź, a gałąź B nadal wskazywałaby na to samo zatwierdzenie abc123, nawet po wielokrotnym popełnieniu tego później. Oznacza to, że te zatwierdzenia stałyby się kandydatami do odśmiecania, gdy HEAD przestanie wskazywać na ostatnie zatwierdzenie w tej „dzikiej” serii zatwierdzeń.
RomainValeri