15%

Zaoszczędź 15% na wszystkich usługach hostingowych

Sprawdź swoje umiejętności i zdobądź Rabat na dowolny plan hostingowy

Użyj kodu:

Skills
Rozpocznij
22.10.2024

Git Reset vs. Git Checkout vs. Git Revert: Kompletny Przewodnik Techniczny

Zrozumienie różnicy między git reset, git checkout i git revert jest niezbędne dla każdego programisty pracującego z kontrolą wersji. Krótko mówiąc: git reset przepisuje historię, przesuwając wskaźnik HEAD; git checkout nawiguje między gałęziami, commitami lub plikami bez zmiany historii; a git revert cofa commit, tworząc nowy odwrotny commit, pozostawiając historię nietkniętą. Wybranie niewłaściwego polecenia — szczególnie na współdzielonej gałęzi — może uszkodzić historię commitów zespołu lub spowodować nieodwracalną utratę danych.

Ten przewodnik wykracza poza powierzchowną składnię, aby wyjaśnić wewnętrzną mechanikę Git, dokładny stan drzewa roboczego i indeksu po każdej operacji oraz rzeczywiste scenariusze, w których każde polecenie jest właściwym (lub katastrofalnie złym) wyborem.

Jak Git zarządza stanem: Trzy drzewa

Przed porównaniem poleceń potrzebujesz solidnego modelu mentalnego tego, jak Git śledzi stan. Git działa na trzech odrębnych warstwach:

  • Katalog roboczy — pliki, które widzisz i edytujesz na dysku.
  • Obszar staging (indeks) — migawka tego, co trafi do następnego commita.
  • Historia commitów (HEAD) — łańcuch niezmiennych obiektów commitów przechowywanych w .git/.

Każde polecenie cofania w Git celuje w jedną lub więcej z tych warstw. Zamieszanie między reset, checkout i revert prawie zawsze wynika z niewiedzenia, których warstw dane polecenie dotyka.

git reset: Przepisywanie lokalnej historii

git reset przesuwa wskaźnik HEAD bieżącej gałęzi do określonego commita. W zależności od przekazanej flagi trybu może również aktualizować indeks i katalog roboczy. Jest to operacja przepisywania historii i powinna być traktowana jako destrukcyjna przy użyciu z --hard.

Wyjaśnienie trybów reset

git reset --soft <commit>   # Move HEAD only; index and working tree unchanged
git reset --mixed <commit>  # Move HEAD + reset index; working tree unchanged (default)
git reset --hard <commit>   # Move HEAD + reset index + reset working tree
TrybHEAD przesuwa sięReset indeksuReset drzewa roboczegoZmiany zachowane
--softTakNieNieStaged i unstaged
--mixedTakTakNieTylko unstaged
--hardTakTakTakŻadne — trwale utracone

Praktyczne przypadki użycia

Squashing commitów przed push. Jeśli masz trzy nieuporządkowane commity work-in-progress, które nie zostały jeszcze wypchnięte, git reset --soft HEAD~3 zwija je z powrotem do indeksu, abyś mógł ponownie zacommitować jako jeden czysty commit.

Usuwanie pliku przypadkowo dodanego do staging. Uruchomienie git reset HEAD <file> (lub git reset bez odniesienia do commita) usuwa plik z indeksu bez dotykania katalogu roboczego — identyczne w działaniu do git restore --staged <file> w Git 2.23+.

Odzyskiwanie po nieudanym lokalnym merge. git reset --hard ORIG_HEAD przywraca stan istniejący bezpośrednio przed merge, ponieważ Git automatycznie zapisuje HEAD sprzed merge do ORIG_HEAD.

Krytyczna pułapka: Push po reset

Jeśli zresetujesz gałąź, która została już wypchnięta do zdalnego repozytorium, a następnie wykonasz force-push, każdy współpracownik, którego lokalna gałąź śledzi to zdalne repozytorium, będzie miał rozbieżną historię. Jest to jedna z najczęstszych przyczyn utraty commitów w środowiskach zespołowych. Nigdy nie uruchamiaj git reset a następnie git push --force na współdzielonej gałęzi bez wyraźnej koordynacji z zespołem.

# Dangerous on shared branches — use only on private/local branches
git reset --hard HEAD~2
git push --force origin feature/my-branch

git checkout: Nawigacja bez modyfikacji historii

git checkout to wielofunkcyjne polecenie, które przełącza drzewo robocze, aby dopasować je do gałęzi, określonego commita lub pojedynczego pliku. Nie modyfikuje historii commitów. W Git 2.23 i nowszych jego odpowiedzialności zostały podzielone na git switch (dla gałęzi) i git restore (dla plików), ale git checkout pozostaje w pełni funkcjonalne i nadal dominuje w środowiskach produkcyjnych.

Dokumentacja składni

git checkout <branch_name>          # Switch to an existing branch
git checkout -b <new_branch>        # Create and switch to a new branch
git checkout <commit_hash>          # Enter detached HEAD state at a specific commit
git checkout -- <file_name>         # Discard working directory changes to a file
git checkout <commit_hash> -- <file> # Restore a single file from a specific commit

Stan detached HEAD: Co to naprawdę oznacza

Gdy uruchamiasz git checkout <commit_hash>, Git przesuwa HEAD tak, aby wskazywał bezpośrednio na obiekt commita, a nie na referencję gałęzi. Nazywa się to stanem detached HEAD. Wszelkie commity wykonane w tym stanie nie są osiągalne z żadnej gałęzi — są osierocone i ostatecznie zostaną usunięte przez garbage collector Git, chyba że utworzysz gałąź, aby je przechwycić.

git checkout 4f7a2c1          # HEAD now points directly to commit 4f7a2c1
git checkout -b hotfix/patch  # Rescue those commits by creating a branch

Typowy scenariusz z życia wzięty: programista sprawdza stary commit, aby przetestować regresję, przypadkowo wprowadza poprawkę, commituje ją, a następnie wraca do main — tracąc poprawkę całkowicie, ponieważ nigdy nie była przypisana do gałęzi.

Przywracanie pojedynczego pliku z historii

Jedną z najbardziej niedocenianych form git checkout jest ukierunkowane przywracanie pliku:

git checkout HEAD~3 -- src/config/database.php

Pobiera to database.php w stanie sprzed trzech commitów bezpośrednio do indeksu i katalogu roboczego, bez dotykania jakiegokolwiek innego pliku lub przesuwania HEAD. Jest to chirurgiczny odpowiednik pełnego przełączenia gałęzi.

git revert: Bezpieczne cofanie dla współdzielonej historii

git revert tworzy nowy commit, który stosuje odwrotny diff określonego commita. Oryginalny commit pozostaje w historii nienaruszony. Jest to jedyny bezpieczny mechanizm cofania dla commitów, które zostały już wypchnięte do współdzielonej zdalnej gałęzi, ponieważ nie przepisuje historii — rozszerza ją.

Dokumentacja składni

git revert HEAD                  # Revert the most recent commit
git revert <commit_hash>         # Revert a specific commit by hash
git revert HEAD~3..HEAD          # Revert a range of commits (creates multiple revert commits)
git revert -n <commit_hash>      # Stage the revert without committing (--no-commit)

Flaga –no-commit: Grupowanie revertów

Podczas cofania wielu commitów tworzenie jednego commita revert na każdy oryginalny commit może zaśmiecać log. Flaga -n (lub --no-commit) umieszcza wszystkie cofnięcia w staging bez commitowania, pozwalając zgrupować je w jeden commit revert:

git revert -n HEAD~4..HEAD
git commit -m "Revert: roll back broken authentication refactor"

Revert commitów merge wymaga szczególnej ostrożności

Cofanie commita merge wymaga określenia nadrzędnej linii głównej za pomocą flagi -m, ponieważ Git musi wiedzieć, którą stronę merge traktować jako "właściwą" historię:

git revert -m 1 <merge_commit_hash>

Tutaj -m 1 wyznacza pierwszego rodzica (zazwyczaj gałąź, do której dokonano merge) jako linię główną. Pominięcie tej flagi przy commicie merge spowoduje, że Git zgłosi błąd. Jest to częsta przeszkoda podczas cofania złego merge wydania w pipeline’ach CI/CD.

Porównanie obok siebie: Reset vs. Checkout vs. Revert

Kryterium`git reset``git checkout``git revert`
Modyfikuje historię commitówTakNieNie (dodaje do niej)
Wpływa na katalog roboczyZ --hard lub --mixedTakTak (poprzez nowy commit)
Wpływa na obszar staging (indeks)Tak (z wyjątkiem --soft)Tylko w formie plikowejNie
Bezpieczne na współdzielonych/zdalnych gałęziachNieTak (tylko do odczytu)Tak
Tworzy nowy commitNieNieTak
OdwracalneCzęściowo (poprzez ORIG_HEAD)TakTak
Obsługuje commity mergeNieTak (nawigacja)Tak (z -m)
Nowoczesny odpowiednik w Git 2.23+Ten samgit switch / git restoreTen sam

Kiedy używać każdego polecenia: Macierz decyzyjna

Używaj git reset gdy:

  • Pracujesz na lokalnej, niewypchnietej gałęzi i chcesz wyczyścić, squashować lub odrzucić commity.
  • Musisz usunąć pliki ze staging przed commitem.
  • Chcesz cofnąć lokalny merge, który nie został udostępniony.
  • Jesteś jedynym programistą na gałęzi funkcji i musisz przepisać jej historię przed otwarciem pull requesta.

Używaj git checkout gdy:

  • Musisz przełączać się między gałęziami podczas aktywnego rozwoju.
  • Chcesz sprawdzić stan repozytorium przy historycznym commicie bez zmieniania czegokolwiek.
  • Musisz przywrócić pojedynczy plik do jego stanu przy określonym commicie.
  • Tworzysz nową gałąź z określonego punktu w historii.

Używaj git revert gdy:

  • Commit został już wypchnięty do zdalnej lub współdzielonej gałęzi.
  • Pracujesz w zespole i musisz utrzymywać przejrzystą, audytowalną historię.
  • Musisz cofnąć określony commit, który nie jest najnowszym (nieliniowy revert).
  • Twój projekt wymaga zgodności lub ścieżek audytu, gdzie usuwanie historii jest zabronione.

Zaawansowany scenariusz: Odzyskiwanie po git reset –hard

Jeśli przypadkowo uruchomiłeś git reset --hard i utraciłeś commity, nie są one natychmiast utracone. Reflog Git rejestruje każdą pozycję, na którą wskazywał HEAD, nawet po hard reset:

git reflog
# Output example:
# a1b2c3d HEAD@{0}: reset: moving to HEAD~3
# 7e8f9a0 HEAD@{1}: commit: Add payment gateway integration
# ...

git reset --hard HEAD@{1}   # Restore to the commit before the accidental reset

Wpisy reflogu wygasają domyślnie po 90 dniach (gc.reflogExpire), więc to okno odzyskiwania nie jest nieskończone. Na serwerze produkcyjnym lub środowisku VPS Hosting uruchamiającym samodzielnie hostowaną usługę Git, taką jak Gitea lub GitLab, powinieneś upewnić się, że katalog .git jest uwzględniony w regularnych procedurach tworzenia kopii zapasowych właśnie z powodu tego wygaśnięcia.

Hostowanie infrastruktury Git

Uruchamianie samodzielnie hostowanego serwera Git — czy to GitLab CE, Gitea, czy Gogs — wymaga niezawodnego I/O pamięci masowej i stałego czasu działania. Pojedynczy uszkodzony plik pack lub przerwany push podczas cyklu git gc może uszkodzić integralność repozytorium. Dla zespołów zarządzających wieloma repozytoriami, Serwer Dedykowany zapewnia izolowane zasoby, pełny dostęp root do precyzyjnego dostrajania ustawień core.packedGitWindowSize i pack.threads Git oraz przepustowość NVMe potrzebną dla dużych monorepo.

Dla mniejszych zespołów lub indywidualnych programistów, którzy potrzebują czystego środowiska Linux do uruchamiania hooków Git, skryptów CI i pipeline’ów wdrożeniowych, VPS z cPanel oferuje zarządzaną płaszczyznę sterowania wraz z pełnym dostępem SSH — eliminując nakład pracy związany z ręczną administracją serwera przy zachowaniu elastyczności konfigurowania hooków po stronie serwera Git i kontroli dostępu.

Jeśli Twój workflow obejmuje automatyczne wdrożenia wyzwalane przez git push, zabezpieczenie serwera ważnym Certyfikatem SSL jest bezwzględnie konieczne — zarówno do szyfrowania ładunków webhook, jak i do uwierzytelniania zdalnych repozytoriów Git opartych na HTTPS bez wyłączania weryfikacji certyfikatu.

Kluczowe wnioski techniczne

  • git reset --hard jest jedynym poleceniem spośród trzech, które może spowodować trwałą, nieodwracalną utratę danych, jeśli reflog wygasł.
  • git revert jest jedynym poleceniem, które jest bezpieczne do użycia po git push bez konieczności force-push.
  • Stan detached HEAD z git checkout <hash> nie usuwa commitów — ale wszelkie nowe commity wykonane w tym stanie będą osierocone, chyba że natychmiast uruchomisz git checkout -b <new_branch>.
  • Flaga -n w git revert jest kluczowa dla utrzymania czystego logu podczas cofania wielu commitów jednocześnie.
  • Git 2.23+ dzieli git checkout na git switch i git restore dla przejrzystości — zrozumienie oryginalnego polecenia sprawia, że obie nowoczesne alternatywy stają się natychmiast intuicyjne.
  • Zawsze weryfikuj bieżącą gałąź za pomocą git status i git log --oneline -5 przed uruchomieniem jakiejkolwiek operacji cofania.
  • Na współdzielonej infrastrukturze egzekwuj reguły ochrony gałęzi (GitHub, GitLab, Gitea — wszystkie to obsługują), aby blokować git push --force na gałęziach main i release na poziomie serwera.

Często zadawane pytania

Jaka jest różnica między git reset --soft a git reset --mixed?

Oba przesuwają HEAD do określonego commita, ale --soft pozostawia zmiany w staging w indeksie, podczas gdy --mixed (domyślny) również czyści indeks, pozostawiając zmiany tylko w katalogu roboczym. Żaden nie dotyka plików na dysku.

Czy mogę odzyskać commity po git reset --hard?

Tak, w oknie wygaśnięcia reflogu (domyślnie 90 dni). Uruchom git reflog, aby znaleźć hash commita utraconego stanu, a następnie uruchom git reset --hard <hash> lub git checkout -b recovery <hash>, aby go przywrócić.

Dlaczego git revert na commicie merge wymaga flagi -m?

Commit merge ma dwóch rodziców. Git nie może określić, którego diffa rodzica odwrócić bez Twojego wskazania linii głównej. -m 1 mówi Git, aby traktował pierwszego rodzica jako trunk, cofając zmiany wprowadzone przez scaloną gałąź.

Czy git checkout -- <file> jest tym samym co git restore <file>?

Funkcjonalnie tak — oba odrzucają niestaged zmiany katalogu roboczego w pliku, przywracając go z indeksu. git restore zostało wprowadzone w Git 2.23 jako mniej niejednoznaczny zamiennik, ale git checkout -- <file> daje identyczne wyniki we wszystkich wersjach Git.

Kiedy absolutnie nigdy nie powinienem używać git reset na gałęzi?

Nigdy nie używaj git reset (szczególnie z --hard lub --mixed) na żadnej gałęzi, którą inni programiści już sklonowali lub pobrali. Spowoduje to rozbieżność ich lokalnej historii ze zdalną, wymagając od każdego współpracownika wykonania wymuszonego resetu lub ponownego klonowania — i ryzykuje ciche odrzucenie commitów istniejących tylko na ich maszynach.

15%

Zaoszczędź 15% na wszystkich usługach hostingowych

Sprawdź swoje umiejętności i zdobądź Rabat na dowolny plan hostingowy

Użyj kodu:

Skills
Rozpocznij