Acest articol explorează diferitele metode de a interacționa cu commit-urile în Git, oferindu-ți o înțelegere clară a modului în care poți manipula istoricul proiectului tău.
Ca dezvoltator, este posibil să te fi confruntat frecvent cu situații în care doreai să te întorci la o versiune anterioară a codului, dar nu știai exact cum să procedezi. Chiar dacă cunoști comenzile de bază Git, cum ar fi reset, revert sau rebase, diferențele dintre ele ar putea fi neclare. Prin urmare, vom analiza în detaliu aceste trei comenzi, pentru a înțelege cum și când să le utilizăm.
Git Reset – Anularea modificărilor
Comanda git reset
este un instrument puternic, utilizat în principal pentru a anula anumite modificări în depozitul tău Git. O poți considera o funcție de „înapoi în timp”.
Cu git reset
, poți naviga între diferitele commit-uri, similar cu derularea unui film. Există trei moduri de funcționare ale comenzii: --soft
, --mixed
și --hard
. Modul implicit este --mixed
. În cadrul procesului git reset
, intervin trei elemente cheie ale Git: HEAD
, zona de pregătire (index) și directorul de lucru.
Directorul de lucru reprezintă locația unde se află fișierele tale și unde lucrezi în mod curent. Poți verifica conținutul acestui director utilizând comanda git status
, care îți va afișa fișierele și directoarele prezente.
Zona de pregătire, sau index, este locul unde Git urmărește și stochează modificările pe care le faci fișierelor. Aceste modificări sunt reflectate în directorul .git
. Pentru a adăuga un fișier în zona de pregătire, folosește git add "nume_fisier"
. Din nou, git status
îți va arăta ce fișiere se află în zona de pregătire.
Ramura curentă în Git este identificată prin HEAD
. Aceasta indică ultimul commit realizat în ramura respectivă. Este practic un pointer către o referință. Când schimbi ramura, HEAD
se mută la noul commit din ramura respectivă.
Să analizăm funcționarea git reset
în cele trei moduri: hard
, soft
și mixed
. Modul hard
mută indicatorul HEAD
la commit-ul specificat, actualizează directorul de lucru cu fișierele acelui commit și resetează zona de pregătire. În modul soft
, se modifică doar pointerul, iar fișierele rămân intacte atât în directorul de lucru, cât și în zona de pregătire. Modul mixed
(cel implicit) resetează atât indicatorul, cât și zona de pregătire.
Git Reset Hard
Scopul git reset --hard
este de a deplasa HEAD
la commit-ul dorit, ștergând toate commit-urile ulterioare. Această operațiune modifică istoricul commit-urilor, reîntorcând proiectul la starea specificată.
În exemplul de mai jos, voi adăuga trei fișiere noi, le voi comita și apoi voi efectua o resetare completă (hard reset).
Mai întâi, verific starea, pentru a mă asigura că nu există modificări necomise.
$ 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
Acum creez trei fișiere noi și adaug conținut în ele.
$ vi file1.txt $ vi file2.txt $ vi file3.txt
Adaug aceste fișiere în depozit.
$ git add file*
Verific din nou starea. Fișierele noi sunt acum în zona de pregătire.
$ 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
Înainte de a comite, vă voi arăta jurnalul cu cele 3 commit-uri existente.
$ git log --oneline 0db602e (HEAD -> master) one more commit 59c86c9 new commit e2f44fc (origin/master, origin/HEAD) test
Acum voi comite aceste fișiere.
$ 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
Cu ls-files
, confirm că fișierele au fost adăugate.
$ git ls-files demo dummyfile newfile file1.txt file2.txt file3.txt
În jurnal, acum am 4 commit-uri, HEAD
indicând ultimul.
$ git log --oneline d69950b (HEAD -> master) added 3 files 0db602e one more commit 59c86c9 new commit e2f44fc (origin/master, origin/HEAD) test
Dacă șterg manual file1.txt
și verific starea, Git îmi spune că modificările nu sunt pregătite pentru commit.
$ 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")
Acum execut o resetare completă (hard reset).
$ git reset --hard HEAD is now at d69950b added 3 files
Dacă verific starea, nu mai am nimic de comitat, iar file1.txt
a revenit. Aceasta se întâmplă deoarece modificarea (ștergerea fișierului) nu a fost comitată. Resetarea completă readuce proiectul la starea din commit-ul indicat.
$ 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
În jurnal, vedem că am revenit la starea commit-ului d69950b
.
$ 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
Scopul resetării hard este de a indica un anumit commit și de a actualiza directorul de lucru și zona de staging. Un alt exemplu: vizualizarea curentă a commit-urilor este:
Voi rula comanda cu HEAD^
, ceea ce înseamnă că vreau să resetez la commit-ul anterior (un commit înapoi).
$ git reset --hard HEAD^ HEAD is now at 0db602e one more commit
Observăm că indicatorul HEAD s-a mutat de la d69950b
la 0db602e
.
$ git log --oneline 0db602e (HEAD -> master) one more commit 59c86c9 new commit e2f44fc (origin/master, origin/HEAD) test
Commit-ul d69950b
a dispărut din jurnal, iar HEAD
indică acum SHA-ul 0db602e
.
$ 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
Cu ls-files
, observăm că fișierele file1.txt
, file2.txt
și file3.txt
nu mai există, deoarece commit-ul care le includea a fost eliminat în urma resetării hard.
$ git ls-files demo dummyfile newfile
Git Soft Reset
În continuare, voi prezenta un exemplu de resetare soft. Presupunem că am adăugat din nou cele 3 fișiere și le-am comitat. Jurnalul arată astfel, cu „soft reset” ca ultimul commit și HEAD
indicând la el.
$ git log --oneline aa40085 (HEAD -> master) soft reset 0db602e one more commit 59c86c9 new commit e2f44fc (origin/master, origin/HEAD) test
Detaliile commit-ului din jurnal sunt afișate cu comanda de mai jos.
$ 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
Acum, folosind resetarea soft, mă voi întoarce la commit-ul cu SHA 0db602e085a4d59cfa9393abac41ff5fd7afcb14
.
Pentru aceasta, voi rula următoarea comandă. Este suficient să specificați primele 6 caractere din SHA.
$ git reset --soft 0db602e085a4
În jurnal, HEAD
a fost mutat la commit-ul indicat.
$ 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
Important, fișierele din commit-ul aa400858aab3927e79116941c715749780a59fc9
(unde am adăugat 3 fișiere) se află încă în directorul de lucru. Ele nu au fost șterse. Acesta este motivul pentru care se recomandă utilizarea resetării soft, și nu a celei hard, când nu vrei să pierzi fișierele.
$ git ls-files demo dummyfile file1.txt file2.txt file3.txt newfile
Git Revert – Anularea modificărilor prin commit
Comanda git revert
este utilizată pentru a anula modificările prin crearea unui nou commit, care inversează modificările aduse de commit-ul specificat. Spre deosebire de reset
, revert
nu elimină date. Putem spune că o comandă git revert
este de fapt un nou commit.
Git revert
menține istoricul commit-urilor, neștergând informații despre commit-urile anterioare.
Presupunem că adaug din nou 3 fișiere și execut un commit, ca în exemplul de mai jos:
$ 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
Jurnalul arată noul commit.
$ git log --oneline 812335d (HEAD -> master) add 3 files again 0db602e one more commit 59c86c9 new commit e2f44fc (origin/master, origin/HEAD) test
Vreau să revin la commit-ul 59c86c9 new commit
. Voi folosi următoarea comandă:
$ git revert 59c86c9
Această comandă va deschide un editor, unde vei găsi detaliile commit-ului la care încerci să revii. Aici poți defini numele noului commit, apoi salvezi și închizi fișierul.
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
După ce salvezi și închizi fișierul, vei obține următorul rezultat:
$ git revert 59c86c9 [master af72b7a] Revert "new commit" 1 file changed, 1 insertion(+), 1 deletion(-)
Revenirea a creat un nou commit, spre deosebire de resetare. În jurnal, se observă noul commit generat de operațiunea de revenire.
$ 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
Jurnalul Git păstrează tot istoricul commit-urilor. Dacă vrei să elimini commit-uri din istoric, revert
nu este o soluție potrivită. Dar, dacă vrei să păstrezi istoricul commit-urilor, cu modificările corespunzătoare, revert
este alternativa corectă la reset
.
Git Rebase – Mutarea și combinarea commit-urilor
În Git, rebase
este utilizat pentru a muta sau a combina commit-urile de pe o ramură pe alta. În general, un dezvoltator nu ar crea funcționalități noi pe ramura principală. Acesta ar lucra pe propria ramură („feature branch”), și odată ce are câteva commit-uri cu funcționalitatea adăugată, le-ar muta pe ramura principală.
Rebase
poate fi uneori dificil de înțeles, deoarece este similar cu o operație de merge
. Scopul ambelor este de a aduce commit-urile din ramura de funcționalitate pe ramura principală (sau orice altă ramură). Să considerăm un grafic ca cel de mai jos:
Într-o echipă cu mai mulți dezvoltatori, acest grafic ar putea deveni complex, cu diferite ramuri de funcționalități și multiple modificări. Rebase
intervine aici pentru a simplifica lucrurile. În loc de git merge
, se utilizează rebase
, pentru a muta commit-urile de pe ramura de funcționalitate pe ramura principală. Practic, rebase
mută commit-urile ramurii de funcționalitate în vârful commit-urilor ramurii principale.
Această abordare oferă un grafic liniar, cu toate commit-urile în ordine.
Astfel, este mai ușor de urmărit ce commit-uri au ajuns unde. Chiar și într-o echipă mare, toate commit-urile sunt în ordine, iar istoricul devine mai simplu de gestionat.
Să vedem cum funcționează practic.
Ramura principală are 4 commit-uri:
$ git log --oneline 812335d (HEAD -> master) add 3 files again 0db602e one more commit 59c86c9 new commit e2f44fc (origin/master, origin/HEAD) test
Voi crea o ramură nouă, numită feature
, bazată pe commit-ul 59c86c9
:
(master) $ git checkout -b feature 59c86c9 Switched to a new branch 'feature'
În ramura feature
, jurnalul arată doar 2 commit-uri preluate de pe ramura principală:
(feature) $ git log --oneline 59c86c9 (HEAD -> feature) new commit e2f44fc (origin/master, origin/HEAD) test
Creez un fișier feature1.txt
și îl adaug în ramura feature
.
(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
Creez al doilea fișier, feature2.txt
și îl commit în ramura feature
.
(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
Acum, în jurnalul ramurii feature
avem două noi commit-uri:
(feature) $ git log --oneline 0f4db49 (HEAD -> feature) feature 2 c639e1b feature 1 59c86c9 new commit e2f44fc (origin/master, origin/HEAD) test
Vreau să adaug aceste două funcționalități pe ramura principală. Pentru aceasta, folosesc comanda rebase
. Din ramura feature
, fac rebase
față de ramura master
, ceea ce ancorează ramura feature
la modificările din ramura master
.
(feature) $ git rebase master Successfully rebased and updated refs/heads/feature.
Apoi verific ramura master
.
(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)
În final, fac rebase
pe ramura principală față de ramura feature
. Aceasta va aduce cele două commit-uri din ramura feature
în vârful ramurii master
.
(master) $ git rebase feature Successfully rebased and updated refs/heads/master.
Dacă verific jurnalul ramurii principale, observ că cele două commit-uri de pe ramura feature
au fost adăugate cu succes.
(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
Acesta a fost totul despre comenzile reset
, revert
și rebase
în Git.
Concluzie
Am analizat comenzile reset
, revert
și rebase
în Git. Sperăm că acest ghid a fost util. Acum ar trebui să poți interacționa cu commit-urile în funcție de nevoile tale, utilizând comenzile descrise în acest articol.