Git Reset vs Revert vs Rebase
V tomto článku se dozvíte o různých způsobech, jak si pohrát s commity v Gitu.
Jako vývojář byste se několikrát setkali s takovými situacemi, kdy byste se chtěli vrátit k jednomu ze svých předchozích odevzdání, ale nebyli byste si jisti, jak to udělat. A i když znáte příkazy Git jako reset, revert, rebase, nejste si vědomi rozdílů mezi nimi. Pojďme tedy začít a porozumět tomu, co je git reset, revert a rebase.
Table of Contents
Git Reset
Git reset je složitý příkaz a používá se k vrácení změn.
Reset git můžete považovat za funkci vrácení zpět. Pomocí git reset můžete přeskakovat mezi různými commity. Existují tři režimy spuštění příkazu git reset: –soft, –mixed a –hard. Ve výchozím nastavení používá příkaz git reset smíšený režim. V pracovním postupu resetování git se do obrazu dostávají tři vnitřní mechanismy správy git: HEAD, pracovní oblast (index) a pracovní adresář.
Pracovní adresář je místo, kde právě pracujete, je to místo, kde jsou vaše soubory. Pomocí příkazu git status můžete vidět, jaké všechny soubory/složky se nacházejí v pracovním adresáři.
Staging Area (Index) je místo, kde git sleduje a ukládá všechny změny v souborech. Uložené změny se projeví v adresáři .git. K přidání souboru do pracovní oblasti použijte git add „filename“. A stejně jako předtím, když spustíte stav git, uvidíte, které soubory jsou přítomny v pracovní oblasti.
Aktuální větev v Gitu se označuje jako HEAD. Ukazuje na poslední potvrzení, ke kterému došlo v aktuální větvi pokladny. Je považováno za ukazatel pro jakýkoli odkaz. Jakmile provedete pokladnu v jiné pobočce, HEAD se také přesune do nové pobočky.
Dovolte mi vysvětlit, jak git reset funguje v tvrdém, měkkém a smíšeném režimu. Tvrdý režim se používá k přechodu do bodovaného odevzdání, pracovní adresář se zaplní soubory tohoto odevzdání a obnoví se pracovní oblast. Při měkkém resetu se změní pouze ukazatel na zadané potvrzení. Soubory všech odevzdání zůstanou před resetem v pracovním adresáři a pracovní oblasti. Ve smíšeném režimu (výchozí) se ukazatel i pracovní oblast resetují.
Tvrdý reset Git
Účelem hard resetu git je přesunout HEAD do zadaného potvrzení. Odebere všechna potvrzení, která se stala po zadaném potvrzení. Tento příkaz změní historii odevzdání a ukáže na zadaný odevzdání.
V tomto příkladu přidám tři nové soubory, odevzdám je a poté provedu tvrdý reset.
Jak můžete vidět z příkazu níže, právě teď není co spáchat.
$ 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
Nyní vytvořím 3 soubory a přidám k nim nějaký obsah.
$ vi file1.txt $ vi file2.txt $ vi file3.txt
Přidejte tyto soubory do stávajícího úložiště.
$ git add file*
Když znovu spustíte příkaz status, bude odrážet nové soubory, které jsem právě vytvořil.
$ 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
Před potvrzením vám dovolte, abych vám ukázal, že v současné době mám v Gitu protokol 3 commitů.
$ git log --oneline 0db602e (HEAD -> master) one more commit 59c86c9 new commit e2f44fc (origin/master, origin/HEAD) test
Nyní se zavazuji k úložišti.
$ 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
Pokud udělám ls-files, uvidíte, že byly přidány nové soubory.
$ git ls-files demo dummyfile newfile file1.txt file2.txt file3.txt
Když spustím příkaz log v git, mám 4 commity a HEAD ukazuje na nejnovější commit.
$ git log --oneline d69950b (HEAD -> master) added 3 files 0db602e one more commit 59c86c9 new commit e2f44fc (origin/master, origin/HEAD) test
Pokud ručně odstraním soubor1.txt a udělám stav git, zobrazí se zpráva, že změny nejsou připraveny pro odevzdání.
$ 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")
Nyní spustím příkaz pro tvrdý reset.
$ git reset --hard HEAD is now at d69950b added 3 files
Pokud znovu zkontroluji stav, zjistím, že není co odevzdávat, a soubor, který jsem smazal, se vrátil do úložiště. K rollbacku došlo, protože po smazání souboru jsem neprovedl potvrzení, takže po tvrdém resetu se vrátil do předchozího stavu.
$ 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
Když zkontroluji protokol git, bude to vypadat takto.
$ 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
Účelem tvrdého resetu je ukázat na zadaný odevzdání a aktualizovat pracovní adresář a pracovní oblast. Dovolte mi ukázat vám ještě jeden příklad. V současné době vypadá vizualizace mých commitů takto:
Zde spustím příkaz s HEAD^, což znamená, že se chci vrátit k předchozímu odevzdání (o jedno potvrzení zpět).
$ git reset --hard HEAD^ HEAD is now at 0db602e one more commit
Můžete vidět, že ukazatel hlavy se nyní změnil na 0db602e z d69950b.
$ git log --oneline 0db602e (HEAD -> master) one more commit 59c86c9 new commit e2f44fc (origin/master, origin/HEAD) test
Pokud zkontrolujete protokol, odevzdání d69950b je pryč a hlava nyní ukazuje 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
Pokud spustíte ls-files, uvidíte, že soubor1.txt, soubor2.txt a soubory3.txt již nejsou v úložišti, protože tento odevzdání a jeho soubor byly po tvrdém resetu odstraněny.
$ git ls-files demo dummyfile newfile
Git Soft Reset
Podobně vám nyní ukážu příklad měkkého resetu. Zvažte, znovu jsem přidal 3 soubory, jak je uvedeno výše, a svěřil jsem je. Zobrazí se protokol git, jak je znázorněno níže. Můžete vidět ‚soft reset‘ je můj nejnovější commit a HEAD na to také poukazuje.
$ git log --oneline aa40085 (HEAD -> master) soft reset 0db602e one more commit 59c86c9 new commit e2f44fc (origin/master, origin/HEAD) test
Podrobnosti o odevzdání v protokolu lze zobrazit pomocí příkazu níže.
$ 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
Nyní pomocí měkkého resetu chci přejít na jeden ze starších commitů s SHA 0db602e085a4d59cfa9393abac41ff5fd7afcb14
K tomu spustím níže uvedený příkaz. Musíte předat více než 6 počátečních znaků SHA, úplný SHA není vyžadován.
$ git reset --soft 0db602e085a4
Nyní, když spustím protokol git, vidím, že HEAD byl resetován na zadaný odevzdání.
$ 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
Rozdíl je ale v tom, že soubory odevzdání (aa400858aab3927e79116941c715749780a59fc9), kam jsem přidal 3 soubory, jsou stále v mém pracovním adresáři. Nebyly smazány. To je důvod, proč byste měli používat měkký reset spíše než tvrdý reset. V měkkém režimu nehrozí ztráta souborů.
$ git ls-files demo dummyfile file1.txt file2.txt file3.txt newfile
Git Revert
V Gitu se příkaz revert používá k provedení operace vrácení, tj. k vrácení některých změn. Je to podobné příkazu reset, ale jediný rozdíl je v tom, že provedete nové potvrzení, abyste se vrátili ke konkrétnímu potvrzení. Stručně řečeno, je fér říci, že příkaz git revert je potvrzení.
Příkaz Git revert při provádění operace vrácení nevymaže žádná data.
Řekněme, že přidávám 3 soubory a provádím operaci potvrzení git pro příklad návratu.
$ 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
Protokol zobrazí nové potvrzení.
$ git log --oneline 812335d (HEAD -> master) add 3 files again 0db602e one more commit 59c86c9 new commit e2f44fc (origin/master, origin/HEAD) test
Nyní bych se rád vrátil k jednomu z mých minulých commitů, řekněme – „59c86c9 new commit“. Spustil bych příkaz níže.
$ git revert 59c86c9
Tím se otevře soubor, najdete podrobnosti o odevzdání, ke kterému se pokoušíte vrátit, a zde můžete pojmenovat svůj nový odevzdání a poté soubor uložit a zavřít.
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 uložení a zavření souboru získáte výstup.
$ git revert 59c86c9 [master af72b7a] Revert "new commit" 1 file changed, 1 insertion(+), 1 deletion(-)
Nyní, aby bylo možné provést potřebné změny, na rozdíl od resetu provedl revert ještě jeden nový odevzdání. Pokud znovu zkontrolujete protokol, najdete nové potvrzení kvůli operaci vrácení.
$ 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
Protokol Git bude obsahovat celou historii odevzdání. Pokud chcete odstranit odevzdání z historie, pak návrat není dobrá volba, ale pokud chcete zachovat změny odevzdání v historii, pak je vhodný příkaz vrátit místo resetu.
Git Rebase
V Gitu je rebase způsob přesouvání nebo kombinování odevzdání jedné větve přes větev jinou. Jako vývojář bych nevytvářel své funkce na hlavní větvi ve scénáři reálného světa. Pracoval bych na své vlastní větvi (‚feature branch‘) a až budu mít ve své feature větvi několik commitů s přidanou funkcí, chtěl bych ji přesunout do hlavní větve.
Rebase může být někdy trochu matoucí k pochopení, protože je velmi podobná sloučení. Cílem sloučení a rebasingu obou je převzít odevzdání z mé větve funkcí a umístit je do hlavní větve nebo jakékoli jiné větve. Zvažte, mám graf, který vypadá takto:
Předpokládejme, že pracujete v týmu s dalšími vývojáři. V takovém případě si dokážete představit, že by to mohlo být opravdu složité, pokud máte spoustu dalších vývojářů pracujících na různých větvích funkcí a tito slučovali několik změn. Sledování se stává matoucím.
Takže tady pomůže rebase. Tentokrát místo začleňování git udělám rebase, kde chci vzít své dvě revize funkce a přesunout je do hlavní větve. Rebase převezme všechny mé odevzdání z větve feature a přesune je na vrchní odevzdání hlavní větve. Takže v zákulisí git duplikuje potvrzení větve feature na hlavní větev.
Tento přístup vám poskytne čistý přímý graf se všemi odevzdáními v řadě.
Usnadňuje sledování toho, kam se odevzdaly. Dokážete si představit, že pokud jste v týmu s mnoha vývojáři, všechny commity jsou stále v řadě. Takže je opravdu snadné sledovat, i když na stejném projektu pracuje mnoho lidí současně.
Dovolte mi, abych vám to ukázal prakticky.
Takto aktuálně vypadá moje hlavní větev. Má 4 commity.
$ git log --oneline 812335d (HEAD -> master) add 3 files again 0db602e one more commit 59c86c9 new commit e2f44fc (origin/master, origin/HEAD) test
Spustím níže uvedený příkaz pro vytvoření a přepnutí na novou větev s názvem feature a tato větev bude vytvořena od 2. odevzdání, tj. 59c86c9
(master) $ git checkout -b feature 59c86c9 Switched to a new branch 'feature'
Pokud zkontrolujete protokol ve větvi feature, má pouze 2 commity pocházející z hlavní (hlavní řady).
(feature) $ git log --oneline 59c86c9 (HEAD -> feature) new commit e2f44fc (origin/master, origin/HEAD) test
Vytvořím prvek 1 a odevzdám jej do větve prvku.
(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
Vytvořím ještě jeden prvek, tj. prvek 2, ve větvi feature a odevzdám ho.
(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
Nyní, když zkontrolujete protokol větve feature, má dva nové odevzdání, které jsem provedl výše.
(feature) $ git log --oneline 0f4db49 (HEAD -> feature) feature 2 c639e1b feature 1 59c86c9 new commit e2f44fc (origin/master, origin/HEAD) test
Nyní chci přidat tyto dvě nové funkce do hlavní větve. K tomu použiji příkaz rebase. Z větve feature budu rebase proti větvi master. Co to udělá, je, že to znovu ukotví mou větev funkcí proti nejnovějším změnám.
(feature) $ git rebase master Successfully rebased and updated refs/heads/feature.
Nyní jdu do toho a zkontroluji hlavní pobočku.
(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)
A nakonec předělejte hlavní větev proti mé hlavní větvi. Tím se převezmou tyto dva nové odevzdání na mé hlavní větvi a přehrají se na mé hlavní větvi.
(master) $ git rebase feature Successfully rebased and updated refs/heads/master.
Nyní, když zkontroluji protokol na hlavní větvi, vidím, že dvě revize mé větve features byly úspěšně přidány do mé hlavní větve.
(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 bylo vše o resetu, vrácení a obnovení příkazů v Gitu.
Závěr
To bylo vše o resetu, vrácení a obnovení příkazů v Gitu. Doufám, že vám tento návod krok za krokem pomohl. Nyní víte, jak si pohrát se svými commity podle potřeby pomocí příkazů uvedených v článku.