Git Reset vs Revert vs Rebase

W tym artykule dowiesz się o różnych sposobach zabawy z rewizjami w Git.

Jako programista wielokrotnie znalazłbyś się w takich sytuacjach, w których chciałbyś cofnąć się do jednego z poprzednich zatwierdzeń, ale nie wiesz, jak to zrobić. I nawet jeśli znasz polecenia Gita, takie jak reset, revert, rebase, nie jesteś świadomy różnic między nimi. Zacznijmy więc od zrozumienia, czym są git reset, revert i rebase.

Resetowanie Gita

Git reset jest złożonym poleceniem i służy do cofania zmian.

Możesz myśleć o resetowaniu git jako o funkcji wycofywania zmian. Dzięki git reset możesz przeskakiwać między różnymi zatwierdzeniami. Istnieją trzy tryby uruchamiania polecenia git reset: –soft, –mixed i –hard. Domyślnie komenda git reset używa trybu mieszanego. W przepływie pracy resetowania git pojawiają się trzy wewnętrzne mechanizmy zarządzania git: HEAD, obszar przemieszczania (indeks) i katalog roboczy.

Katalog roboczy to miejsce, w którym aktualnie pracujesz, to miejsce, w którym znajdują się Twoje pliki. Za pomocą polecenia git status możesz zobaczyć, jakie wszystkie pliki/foldery znajdują się w katalogu roboczym.

Obszar przejściowy (indeks) to miejsce, w którym git śledzi i zapisuje wszystkie zmiany w plikach. Zapisane zmiany są odzwierciedlane w katalogu .git. Używasz git add „nazwa pliku”, aby dodać plik do obszaru testowego. I tak jak poprzednio, kiedy uruchomisz git status, zobaczysz, które pliki są obecne w obszarze pomostowym.

Bieżąca gałąź w Git jest określana jako HEAD. Wskazuje na ostatnie zatwierdzenie, które miało miejsce w bieżącej gałęzi checkout. Jest traktowany jako wskaźnik dla każdego odniesienia. Gdy przejdziesz do kasy w innym oddziale, HEAD również przeniesie się do nowego oddziału.

Pozwól, że wyjaśnię, jak działa reset git w trybach twardym, miękkim i mieszanym. Tryb twardy służy do przejścia do wskazanego zatwierdzenia, katalog roboczy zostaje wypełniony plikami tego zatwierdzenia, a obszar przemieszczania zostaje zresetowany. Podczas miękkiego resetowania tylko wskaźnik jest zmieniany na określone zatwierdzenie. Pliki wszystkich zatwierdzeń pozostają przed zresetowaniem w katalogu roboczym i obszarze przejściowym. W trybie mieszanym (domyślnym) zarówno wskaźnik, jak i obszar przejściowy są resetowane.

Twardy reset Gita

Celem twardego resetu git jest przeniesienie HEAD do określonego zatwierdzenia. Usunie wszystkie zatwierdzenia, które miały miejsce po określonym zatwierdzeniu. To polecenie zmieni historię zatwierdzeń i wskaże określone zatwierdzenie.

W tym przykładzie dodam trzy nowe pliki, zatwierdzę je, a następnie wykonam twardy reset.

Jak widać z poniższego polecenia, w tej chwili nie ma nic do zatwierdzenia.

$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.

(use "git push" to publish your local commits)

nothing to commit, working tree clean

Teraz utworzę 3 pliki i dodam do nich trochę treści.

$ vi file1.txt
$ vi file2.txt
$ vi file3.txt

Dodaj te pliki do istniejącego repozytorium.

$ git add file*

Gdy ponownie uruchomisz polecenie statusu, będzie ono odzwierciedlać nowe pliki, które właśnie utworzyłem.

$ git status
On branch master
Your branch is ahead of 'origin/master' by 2 commits.

(use "git push" to publish your local commits)

Changes to be committed:

(use "git restore --staged <file>..." to unstage)

new file:
file1.txt

new file:
file2.txt

new file:
file3.txt

Zanim zatwierdzę, pozwól, że ci pokażę, że obecnie mam dziennik 3 zatwierdzeń w Git.

$ git log --oneline
0db602e (HEAD -> master) one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test

Teraz zobowiązuję się do repozytorium.

$ git commit -m 'added 3 files'
[master d69950b] added 3 files
3 files changed, 3 insertions(+)
create mode 100644 file1.txt
create mode 100644 file2.txt
create mode 100644 file3.txt

Jeśli zrobię ls-files, zobaczysz, że nowe pliki zostały dodane.

$ git ls-files
demo
dummyfile
newfile
file1.txt
file2.txt
file3.txt

Kiedy uruchamiam polecenie log w git, mam 4 zatwierdzenia, a HEAD wskazuje na ostatnie zatwierdzenie.

$ git log --oneline
d69950b (HEAD -> master) added 3 files
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test

Jeśli pójdę i ręcznie usunę plik file1.txt i wykonam status git, wyświetli się komunikat, że zmiany nie są przygotowane do zatwierdzenia.

$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commits.

(use "git push" to publish your local commits)

Changes not staged for commit:

(use "git add/rm <file>..." to update what will be committed)

(use "git restore <file>..." to discard changes in working directory)

deleted:
file1.txt

no changes added to commit (use "git add" and/or "git commit -a")

Teraz uruchomię polecenie twardego resetu.

$ git reset --hard
HEAD is now at d69950b added 3 files

Jeśli ponownie sprawdzę status, stwierdzę, że nie ma nic do zatwierdzenia, a usunięty plik wrócił do repozytorium. Wycofanie nastąpiło, ponieważ po usunięciu pliku nie popełniłem, więc po twardym resecie wrócił do poprzedniego stanu.

$ git status
On branch master
Your branch is ahead of 'origin/master' by 3 commits.

(use "git push" to publish your local commits)

nothing to commit, working tree clean

Jeśli sprawdzę dziennik git, tak to będzie wyglądać.

$ git log
commit d69950b7ea406a97499e07f9b28082db9db0b387 (HEAD -> master)
Author: mrgeek <[email protected]>
Date:
Mon May 17 19:53:31 2020 +0530

added 3 files

commit 0db602e085a4d59cfa9393abac41ff5fd7afcb14
Author: mrgeek <[email protected]>
Date:
Mon May 17 01:04:13 2020 +0530

one more commit

commit 59c86c96a82589bad5ecba7668ad38aa684ab323
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:54:53 2020 +0530

new commit

commit e2f44fca2f8afad8e4d73df6b72111f2f2fd71ad (origin/master, origin/HEAD)
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:16:33 2020 +0530

test

Celem twardego resetu jest wskazanie określonego zatwierdzenia i zaktualizowanie katalogu roboczego i obszaru przemieszczania. Pokażę wam jeszcze jeden przykład. Obecnie wizualizacja moich commitów wygląda jak poniżej:

Tutaj uruchomię polecenie z HEAD^, co oznacza, że ​​​​chcę zresetować do poprzedniego zatwierdzenia (jeden zatwierdzenie wstecz).

$ git reset --hard HEAD^
HEAD is now at 0db602e one more commit

Możesz zobaczyć, że wskaźnik głowy zmienił się teraz na 0db602e z d69950b.

$ git log --oneline
0db602e (HEAD -> master) one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test

Jeśli sprawdzisz dziennik, zatwierdzenie d69950b zniknęło, a głowa wskazuje teraz na 0db602e SHA.

$ git log
commit 0db602e085a4d59cfa9393abac41ff5fd7afcb14 (HEAD -> master)
Author: mrgeek <[email protected]>
Date:
Mon May 17 01:04:13 2020 +0530

one more commit

commit 59c86c96a82589bad5ecba7668ad38aa684ab323
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:54:53 2020 +0530

new commit

commit e2f44fca2f8afad8e4d73df6b72111f2f2fd71ad (origin/master, origin/HEAD)
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:16:33 2020 +0530

Test

Jeśli uruchomisz pliki ls, zobaczysz, że plik1.txt, plik2.txt i pliki3.txt nie znajdują się już w repozytorium, ponieważ to zatwierdzenie i jego plik zostały usunięte po twardym resecie.

$ git ls-files
demo
dummyfile
newfile

Miękki reset Gita

Podobnie, teraz pokażę ci przykład miękkiego resetu. Rozważ, ponownie dodałem 3 pliki, jak wspomniano powyżej, i zatwierdziłem je. Dziennik git pojawi się, jak pokazano poniżej. Możesz zobaczyć, że „miękki reset” to moje ostatnie zatwierdzenie, a HEAD również na to wskazuje.

$ git log --oneline
aa40085 (HEAD -> master) soft reset
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test

Szczegóły zatwierdzenia w dzienniku można zobaczyć za pomocą poniższego polecenia.

$ git log
commit aa400858aab3927e79116941c715749780a59fc9 (HEAD -> master)
Author: mrgeek <[email protected]>
Date:
Mon May 17 21:01:36 2020 +0530

soft reset

commit 0db602e085a4d59cfa9393abac41ff5fd7afcb14
Author: mrgeek <[email protected]>
Date:
Mon May 17 01:04:13 2020 +0530

one more commit

commit 59c86c96a82589bad5ecba7668ad38aa684ab323
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:54:53 2020 +0530

new commit

commit e2f44fca2f8afad8e4d73df6b72111f2f2fd71ad (origin/master, origin/HEAD)
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:16:33 2020 +0530

test

Teraz, korzystając z miękkiego resetu, chcę przełączyć się na jedno ze starszych zatwierdzeń za pomocą SHA 0db602e085a4d59cfa9393abac41ff5fd7afcb14

Aby to zrobić, uruchomię poniższe polecenie. Musisz podać więcej niż 6 początkowych znaków SHA, pełne SHA nie jest wymagane.

$ git reset --soft 0db602e085a4

Teraz, kiedy uruchamiam dziennik git, widzę, że HEAD został zresetowany do określonego przeze mnie zatwierdzenia.

$ git log
commit 0db602e085a4d59cfa9393abac41ff5fd7afcb14 (HEAD -> master)
Author: mrgeek <[email protected]>
Date:
Mon May 17 01:04:13 2020 +0530

one more commit

commit 59c86c96a82589bad5ecba7668ad38aa684ab323
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:54:53 2020 +0530

new commit

commit e2f44fca2f8afad8e4d73df6b72111f2f2fd71ad (origin/master, origin/HEAD)
Author: mrgeek <[email protected]>
Date:
Mon May 17 00:16:33 2020 +0530

test

Ale różnica polega na tym, że pliki zatwierdzenia (aa400858aab3927e79116941c715749780a59fc9), w których dodałem 3 pliki, nadal znajdują się w moim katalogu roboczym. Nie zostały usunięte. Dlatego powinieneś użyć miękkiego resetu zamiast twardego resetu. W trybie miękkim nie ma ryzyka utraty plików.

$ git ls-files
demo
dummyfile
file1.txt
file2.txt
file3.txt
newfile

Przywróć Gita

W Git polecenie revert służy do wykonywania operacji przywracania, tj. do cofania niektórych zmian. Jest to podobne do polecenia resetowania, ale jedyną różnicą jest to, że wykonujesz nowe zatwierdzenie, aby wrócić do konkretnego zatwierdzenia. Krótko mówiąc, można śmiało powiedzieć, że polecenie git revert jest zatwierdzeniem.

Polecenie Git revert nie usuwa żadnych danych podczas wykonywania operacji przywracania.

Powiedzmy, że dodaję 3 pliki i wykonuję operację zatwierdzenia git dla przykładu przywracania.

$ git commit -m 'add 3 files again'
[master 812335d] add 3 files again
3 files changed, 3 insertions(+)
create mode 100644 file1.txt
create mode 100644 file2.txt
create mode 100644 file3.txt

Dziennik pokaże nowe zatwierdzenie.

$ git log --oneline
812335d (HEAD -> master) add 3 files again
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test

Teraz chciałbym wrócić do jednego z moich wcześniejszych zatwierdzeń, powiedzmy – „59c86c9 nowe zatwierdzenie”. Uruchomiłbym poniższe polecenie.

$ git revert 59c86c9

Spowoduje to otwarcie pliku, znajdziesz szczegóły zatwierdzenia, do którego próbujesz powrócić, i możesz nadać nowemu zatwierdzeniu nazwę tutaj, a następnie zapisać i zamknąć plik.

Revert "new commit"

This reverts commit 59c86c96a82589bad5ecba7668ad38aa684ab323.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
# Your branch is ahead of 'origin/master' by 4 commits.
# (use "git push" to publish your local commits)
#
# Changes to be committed:
# modified: dummyfile

Po zapisaniu i zamknięciu pliku otrzymasz taki wynik.

$ git revert 59c86c9
[master af72b7a] Revert "new commit"
1 file changed, 1 insertion(+), 1 deletion(-)

Teraz, aby wprowadzić niezbędne zmiany, w przeciwieństwie do resetowania, revert wykonał jeszcze jedno nowe zatwierdzenie. Jeśli ponownie sprawdzisz dziennik, znajdziesz nowe zatwierdzenie z powodu operacji przywracania.

$ git log --oneline
af72b7a (HEAD -> master) Revert "new commit"
812335d add 3 files again
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test

Dziennik Git będzie zawierał całą historię zatwierdzeń. Jeśli chcesz usunąć zatwierdzenia z historii, to revert nie jest dobrym wyborem, ale jeśli chcesz zachować zmiany zatwierdzenia w historii, to revert jest odpowiednim poleceniem zamiast resetowania.

Git Rebase

W Git rebase to sposób przenoszenia lub łączenia zatwierdzeń jednej gałęzi nad inną gałęzią. Jako programista nie tworzyłbym swoich funkcji w gałęzi głównej w rzeczywistym scenariuszu. Pracowałbym na własnej gałęzi („gałąź funkcji”), a kiedy mam kilka zatwierdzeń w mojej gałęzi funkcji z dodaną funkcją, chciałbym przenieść ją do gałęzi głównej.

Rebase może czasami być trochę mylący do zrozumienia, ponieważ jest bardzo podobny do scalania. Celem połączenia i zmiany bazy obu jest pobranie zatwierdzeń z mojej gałęzi fabularnej i umieszczenie ich w gałęzi głównej lub dowolnej innej. Rozważ, mam wykres, który wygląda tak:

Załóżmy, że pracujesz w zespole z innymi programistami. W takim przypadku możesz sobie wyobrazić, że może to być naprawdę skomplikowane, gdy masz grupę innych programistów pracujących nad różnymi gałęziami funkcji i łączących wiele zmian. Śledzenie staje się mylące.

Więc tutaj rebase pomoże. Tym razem, zamiast git merge, zrobię rebase, gdzie chcę wziąć moje dwa rewizje z gałęzi funkcji i przenieść je do gałęzi głównej. Rebase pobierze wszystkie moje zatwierdzenia z gałęzi funkcji i przeniesie je na wierzch zatwierdzeń gałęzi głównej. Tak więc za kulisami git powiela zatwierdzenia gałęzi funkcji w gałęzi głównej.

Takie podejście da ci czysty wykres liniowy ze wszystkimi zatwierdzeniami z rzędu.

Ułatwia śledzenie, które zatwierdzenia trafiły dokąd. Możesz sobie wyobrazić, że jeśli jesteś w zespole z wieloma programistami, wszystkie zatwierdzenia wciąż są z rzędu. Tak więc jest to naprawdę łatwe do naśladowania, nawet jeśli wiele osób pracuje nad tym samym projektem w tym samym czasie.

Pokażę ci to praktycznie.

Tak obecnie wygląda moja główna gałąź. Ma 4 zobowiązania.

$ git log --oneline
812335d (HEAD -> master) add 3 files again
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test

Uruchomię poniższe polecenie, aby utworzyć i przełączyć się na nową gałąź o nazwie funkcja, a ta gałąź zostanie utworzona z drugiego zatwierdzenia, tj. 59c86c9

(master)
$ git checkout -b feature 59c86c9
Switched to a new branch 'feature'

Jeśli sprawdzisz dziennik w gałęzi funkcji, ma on tylko 2 zatwierdzenia pochodzące od mastera (linia główna).

(feature)
$ git log --oneline
59c86c9 (HEAD -> feature) new commit
e2f44fc (origin/master, origin/HEAD) test

Stworzę funkcję 1 i zatwierdzę ją w gałęzi funkcji.

(feature)
$ vi feature1.txt

(feature)
$ git add .
The file will have its original line endings in your working directory

(feature)
$ git commit -m 'feature 1'
[feature c639e1b] feature 1
1 file changed, 1 insertion(+)
create mode 100644 feature1.txt

Stworzę jeszcze jedną cechę, tj. cechę 2, w gałęzi funkcji i zatwierdzę ją.

(feature)
$ vi feature2.txt

(feature)
$ git add .
The file will have its original line endings in your working directory

(feature)
$ git commit -m 'feature 2'
[feature 0f4db49] feature 2
1 file changed, 1 insertion(+)
create mode 100644 feature2.txt

Teraz, jeśli sprawdzisz dziennik gałęzi funkcji, ma ona dwa nowe zatwierdzenia, które wykonałem powyżej.

(feature)
$ git log --oneline
0f4db49 (HEAD -> feature) feature 2
c639e1b feature 1
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test

Teraz chcę dodać te dwie nowe funkcje do gałęzi master. W tym celu użyję polecenia rebase. Z gałęzi funkcji wykonam bazowanie względem gałęzi głównej. To, co to zrobi, to ponowne zakotwiczenie mojej gałęzi funkcji w stosunku do najnowszych zmian.

(feature)
$ git rebase master
Successfully rebased and updated refs/heads/feature.

Teraz idę dalej i sprawdzę gałąź główną.

(feature)
$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 3 commits.

(use "git push" to publish your local commits)

I na koniec zmień bazę gałęzi master na moją gałąź funkcji. Spowoduje to pobranie tych dwóch nowych zatwierdzeń z mojej gałęzi funkcji i odtworzenie ich na górze mojej gałęzi głównej.

(master)
$ git rebase feature
Successfully rebased and updated refs/heads/master.

Teraz, gdy sprawdzę dziennik w gałęzi głównej, widzę, że dwa zatwierdzenia mojej gałęzi funkcji zostały pomyślnie dodane do mojej gałęzi głównej.

(master)
$ git log --oneline
766c996 (HEAD -> master, feature) feature 2
c036a11 feature 1
812335d add 3 files again
0db602e one more commit
59c86c9 new commit
e2f44fc (origin/master, origin/HEAD) test

To było wszystko o poleceniach reset, revert i rebase w Git.

Wniosek

To było wszystko o poleceniach reset, revert i rebase w Git. Mam nadzieję, że ten przewodnik krok po kroku był pomocny. Teraz wiesz, jak bawić się swoimi zatwierdzeniami zgodnie z potrzebami, używając poleceń wymienionych w artykule.