Гит Ресет вс Реверт вс Ребасе

U ovom tekstu ćemo istražiti različite metode manipulacije komitima u sistemu Git.

Kao programer, često se susrećete sa situacijama u kojima želite da se vratite na prethodne verzije koda, ali niste sigurni kako to da uradite. Iako možda znate Git komande poput reset, revert i rebase, često niste svesni razlika među njima. Zato, hajde da započnemo i objasnimo šta znače Git reset, revert i rebase.

Git Reset

Git reset je kompleksna komanda koja se koristi za poništavanje promena.

Možete zamisliti Git reset kao funkciju za vraćanje na prethodno stanje. Korišćenjem Git reseta, možete se kretati između različitih komita. Postoje tri načina na koji se može koristiti Git reset komanda: –soft, –mixed i –hard. Po defaultu, Git reset komanda koristi mixed režim. Tokom procesa resetovanja, tri ključna Git mehanizma stupaju na scenu: HEAD, staging area (indeks) i radni direktorijum.

Radni direktorijum je mesto gde trenutno radite, odnosno gde se nalaze vaše datoteke. Korišćenjem komande git status, možete videti sve datoteke i foldere unutar radnog direktorijuma.

Staging Area (indeks) je prostor gde Git prati i čuva sve izmene datoteka. Sačuvane izmene se ogledaju u .git direktorijumu. Datoteku u staging area možete dodati pomoću komande git add „ime datoteke“. Kao i pre, kada koristite git status videćete koje datoteke su u staging area.

Trenutna grana u Gitu se naziva HEAD. Ona pokazuje na poslednji komit koji se desio u trenutnoj grani. Tretira se kao pokazivač za bilo koju referencu. Kada pređete u drugu granu, HEAD se takođe pomera na novu granu.

Dozvolite da objasnim kako Git reset funkcioniše u hard, soft i mixed modu. Hard mod se koristi za prelazak na određeni komit, radni direktorijum se popunjava datotekama iz tog komita, a staging area se resetuje. U soft resetu, menja se samo pokazivač na određeni komit. Datoteke iz svih komita pre reseta ostaju u radnom direktorijumu i staging area. U mixed modu (što je default), pokazivač i staging area se resetuju.

Git Reset Hard

Svrha hard reseta je da pomeri HEAD na specifičan komit. On će ukloniti sve komite koji su se desili nakon navedenog komita. Ova komanda će promeniti istoriju komita i vratiti je na željeni komit.

U ovom primeru ću dodati tri nove datoteke, komitovati ih i zatim izvršiti hard reset.

Kao što možete videti iz naredbe ispod, trenutno nema ništa za komitovanje.

$ 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

Sada ću kreirati tri datoteke i dodati im sadržaj.

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

Dodajte ove datoteke u postojeći repozitorijum.

$ git add file*

Kada ponovo pokrenete komandu status, ona će pokazati nove datoteke koje sam upravo kreirao.

$ 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

Pre komitovanja, da vam pokažem, trenutno imam log od tri komita u Git-u.

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

Sada ću komitovati promene u repozitorijum.

$ 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

Ako uradim ls-files, videćete da su nove datoteke dodate.

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

Kada pokrenem komandu log u Git-u, imam 4 komita i HEAD pokazuje na najnoviji komit.

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

Ako odem i ručno obrišem file1.txt i izvršim git status, prikazaće se poruka da promene nisu postavljene za komit.

$ 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")

Sada ću pokrenuti komandu za hard reset.

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

Ako ponovo proverim status, otkriću da nema šta za komitovanje, a datoteka koju sam obrisao se vratila u repozitorijum. Vraćanje se desilo jer nakon brisanja datoteke nisam komitovao, pa se nakon hard reseta vratio u prethodno stanje.

$ 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

Ako proverim Git log, izgledaće ovako.

$ 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

Svrha hard reseta je da pokaže na određeni komit i ažurira radni direktorijum i staging area. Da vam pokažem još jedan primer. Trenutno, vizualizacija mojih komita izgleda ovako:

Ovde ću pokrenuti komandu sa HEAD^, što znači da želim da se vratim na prethodni komit (jedan komit unazad).

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

Možete videti da je pokazivač glave sada promenjen na 0db602e sa d69950b.

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

Ako proverite log, komit d69950b je nestao, a glava sada pokazuje 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

Ako pokrenete ls-files, možete videti da file1.txt, file2.txt i files3.txt više nisu u repozitorijumu jer su taj komit i njegove datoteke uklonjeni nakon hard reseta.

$ git ls-files
demo
dummyfile
newfile

Git Soft Reset

Slično tome, sada ću vam pokazati primer soft reseta. Uzmite u obzir, ponovo sam dodao tri datoteke kao što je gore pomenuto i komitovao ih. Git log će se pojaviti kao što je prikazano ispod. Možete videti da je ‘soft reset’ moj najnoviji komit, a HEAD takođe pokazuje na njega.

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

Detalji komita u logu se mogu videti pomoću donje komande.

$ 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

Sada, koristeći soft reset, želim da se vratim na jedan od starijih komita sa SHA 0db602e085a4d59cfa9393abac41ff5fd7afcb14.

Da bih to uradio, pokrenuću naredbu ispod. Morate proslediti više od 6 početnih karaktera SHA, kompletan SHA nije neophodan.

$ git reset --soft 0db602e085a4

Sada kada pokrenem git log, vidim da je HEAD resetovan na komit koji sam naveo.

$ 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

Ali razlika je u tome što su datoteke komita (aa400858aab3927e79116941c715749780a59fc9) gde sam dodao tri datoteke još uvek u mom radnom direktorijumu. Nisu izbrisane. Zato bi trebalo da koristite soft reset umesto hard reseta. Ne postoji rizik od gubljenja datoteka u soft modu.

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

Git Revert

U Gitu, komanda revert se koristi za izvršavanje operacije vraćanja, odnosno za poništavanje nekih promena. Slična je komandi za reset, ali jedina razlika je u tome što kreirate novi komit da biste se vratili na određeni komit. Ukratko, može se reći da je komanda git revert u stvari komit.

Komanda git revert ne briše nikakve podatke tokom operacije vraćanja.

Recimo da dodajem tri datoteke i izvršavam git operaciju komitovanja za primer vraćanja.

$ 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

Log će pokazati novi komit.

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

Sada bih želeo da se vratim na jedan od mojih prošlih komita, recimo – „59c86c9 new commit“. Pokrenuo bih naredbu ispod.

$ git revert 59c86c9

Ovo će otvoriti datoteku, naći ćete detalje komita na koji pokušavate da se vratite, i možete da date ime novom komitu, a zatim sačuvate i zatvorite datoteku.

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

Nakon što sačuvate i zatvorite datoteku, ovo je izlaz koji ćete dobiti.

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

Sada, da izvrši neophodne promene, za razliku od reseta, revert je izvršio još jedan novi komit. Ako ponovo proverite log, naći ćete novi komit zbog operacije vraćanja.

$ 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

Git log će imati celokupnu istoriju komita. Ako želite da uklonite komit iz istorije, onda vraćanje nije dobar izbor, ali ako želite da zadržite promene komita u istoriji, onda je revert odgovarajuća komanda umesto reseta.

Git Rebase

U Gitu, rebase je način premeštanja ili kombinovanja komita jedne grane preko druge grane. Kao programer, ne bi trebalo da kreirate svoje funkcionalnosti na glavnoj grani u realnom scenariju. Radili biste na sopstvenoj grani („ograničena grana“), a kada imate nekoliko komita u svojoj grani sa dodatnom funkcijom, tada biste želeli da je prebacite u glavnu granu.

Rebase ponekad može biti malo zbunjujuć za razumevanje jer je veoma slično spajanju (merge). Cilj spajanja i rebaze-a je da preuzmu komite iz vaše grane funkcionalnosti i prebace ih na glavnu granu ili bilo koju drugu granu. Uzmite u obzir, grafikon koji izgleda ovako:

Pretpostavimo da radite u timu sa drugim programerima. U tom slučaju, možete zamisliti da ovo može postati veoma složeno kada imate mnoštvo drugih programera koji rade na različitim granama funkcionalnosti, a oni spajaju više promena. Postaje komplikovano za praćenje.

Dakle, ovde će rebase pomoći. Ovog puta, umesto da koristim git merge, uradiću rebase, gde želim da preuzmem svoja dva komita grane funkcionalnosti i premestim ih na glavnu granu. Rebase će preuzeti sve moje komite iz grane funkcije i prebaciti ih na vrh komita glavne grane. Dakle, iza scene, Git duplira komite grane funkcionalnosti na glavnoj grani.

Ovaj pristup će vam dati čist, pravolinijski grafikon sa svim komitima u nizu.

Olakšava praćenje gde su komiti otišli. Možete zamisliti da ako ste u timu sa puno programera, svi komiti su i dalje u nizu. Dakle, zaista je lako pratiti čak i ako imate puno ljudi koji rade na istom projektu u isto vreme.

Dozvolite da vam ovo praktično pokažem.

Ovako trenutno izgleda moja glavna grana. Ima 4 komita.

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

Pokrenuću naredbu ispod da kreiram i prebacim se na novu granu koja se zove feature, a ova grana će biti kreirana od drugog komita, odnosno 59c86c9.

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

Ako proverite log u grani feature, ona ima samo 2 komita koja dolaze od master (glavne) linije.

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

Kreiraću feature 1 i komitovati ga u granu 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

Kreiraću još jednu funkcionalnost, odnosno feature 2, u grani feature i komitovati je.

(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

Sada, ako proverite log grane feature, ima dva nova komita koja sam izvršio iznad.

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

Sada želim da dodam ove dve nove funkcionalnosti u glavnu granu. Za to ću koristiti komandu rebase. Iz grane feature, rebase-ovaću u odnosu na glavnu granu. Ono što će ovo uraditi je da će ponovo usidriti moju granu feature u odnosu na najnovije promene.

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

Sada ću otići i proveriti glavnu granu.

(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 kraju, ponovo postavite glavnu granu u odnosu na moju granu feature. Ovo će uzeti ta dva nova komita na mojoj grani feature i ponovo ih reprodukovati na vrhu moje glavne grane.

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

Sada, ako proverim log na glavnoj grani, mogu videti da su dva komita moje grane feature uspešno dodata u moju glavnu granu.

(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 je sve o reset, revert i rebase komandama u Git-u.

Zaključak

To je bilo sve o reset, revert i rebase komandama u Git-u. Nadam se da je ovaj vodič korak po korak bio od pomoći. Sada znate kako da manipulišete svojim komitima po potrebi koristeći komande pomenute u članku.