Git-triks: Mestre reset, revert og rebase!

I denne artikkelen skal vi utforske de forskjellige måtene du kan manipulere commits på i Git.

Som utvikler vil du ofte befinne deg i situasjoner hvor du ønsker å gå tilbake til en tidligere versjon av koden din, men du er usikker på hvordan du gjør det. Selv om du kanskje er kjent med Git-kommandoer som reset, revert og rebase, er det ikke alltid like tydelig hva de ulike alternativene innebærer. La oss derfor se nærmere på hva git reset, revert og rebase egentlig er, og hvordan de brukes.

Git Reset

Git reset er en allsidig kommando som brukes til å angre endringer i Git-repositoriet.

Du kan tenke på git reset som en «tilbake»-funksjon som lar deg hoppe frem og tilbake mellom ulike commits. Git reset har tre ulike moduser: –soft, –mixed og –hard. Som standard bruker git reset blandet modus. Når du jobber med git reset, er det tre sentrale elementer i Git som spiller en rolle: HEAD, staging area (indeks), og arbeidsmappen.

Arbeidsmappen er der du jobber med filene dine. Du kan bruke kommandoen git status for å se hvilke filer og mapper som finnes i arbeidsmappen din.

Staging area (indeks) er et mellomlager der Git sporer og lagrer alle endringer du har gjort i filene dine. Disse endringene gjenspeiles i .git-mappen. Du bruker kommandoen git add <filnavn> for å legge til filer i staging area. På samme måte kan du bruke git status for å se hvilke filer som ligger i staging area.

Den aktuelle grenen i Git refereres til som HEAD. Den peker på den siste commiten i den aktuelle grenen. Du kan se på HEAD som en peker som flyttes når du sjekker ut en annen gren.

La oss se hvordan git reset fungerer i de tre forskjellige modusene (hard, soft og mixed). Hard modus brukes til å flytte HEAD til den ønskede commiten, og arbeidsmappen fylles med filene fra den commiten. Staging area blir også tilbakestilt. Ved soft reset endres bare pekeren til den angitte commiten. Alle filer forblir i arbeidsmappen og staging area som de var før tilbakestillingen. I mixed modus (som er standard), tilbakestilles både pekeren og staging area.

Git Reset Hard

Hensikten med git reset --hard er å flytte HEAD til en spesifikk commit. Dette vil fjerne alle commits som har skjedd etter den spesifiserte commiten. Kommandoen endrer commit-historikken og peker til den ønskede commiten.

I dette eksemplet vil jeg demonstrere dette ved å legge til tre nye filer, committe dem, og deretter utføre en hard reset.

Som du kan se fra kommandoen nedenfor, er det akkurat nå ingenting å committe.

$ 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

Nå lager jeg 3 filer og legger til noe innhold i dem.

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

Legg til filene i repoet.

$ git add file*

Kjører jeg status-kommandoen nå, vil vi se de nye filene jeg akkurat opprettet.

$ 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

Før jeg committer, la meg vise deg at jeg har en logg med 3 commits i Git.

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

Nå committer jeg til repoet.

$ 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

Hvis jeg kjører ls-filer, ser du at de nye filene er lagt til.

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

Når jeg kjører log-kommandoen i git, har jeg 4 commits og HEAD peker på den siste commiten.

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

Hvis jeg sletter file1.txt manuelt og kjører en git-status, vil jeg se meldingen om at endringene ikke er iscenesatt for 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")

Nå kjører jeg en hard reset.

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

Hvis jeg sjekker statusen igjen, ser jeg at det ikke er noe å committe, og filen jeg slettet, er tilbake i repoet. Tilbakestillingen skjedde fordi jeg ikke hadde commitet etter at jeg slettet filen. Etter en hard reset er jeg tilbake til forrige tilstand.

$ 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

Hvis jeg sjekker git loggen, ser det slik ut.

$ 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

Hensikten med hard reset er å peke til den spesifiserte commiten og oppdatere arbeidsmappen og staging area. La meg vise deg et eksempel til. For øyeblikket ser visualiseringen av mine commits slik ut:

Her kjører jeg kommandoen med HEAD^, som betyr at jeg tilbakestiller til forrige commit (en commit tilbake).

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

Du ser at hodepekeren nå er endret til 0db602e fra d69950b.

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

Sjekker du loggen, ser du at commiten d69950b er borte, og hodet peker nå på SHA 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

Hvis du kjører ls-filer, ser du at file1.txt, file2.txt og file3.txt ikke lenger finnes i repoet, fordi den commiten og filene den inneholdt er fjernet etter den harde reseten.

$ git ls-files
demo
dummyfile
newfile

Git Soft Reset

La meg nå vise deg et eksempel på en soft reset. Tenk deg at jeg har lagt til de 3 filene igjen som nevnt ovenfor, og commitet dem. Git loggen vil se ut som nedenfor. Du ser at «soft reset» er min siste commit, og at HEAD peker til den.

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

Detaljer om commiten i loggen kan sees med kommandoen nedenfor.

$ 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

Når jeg bruker soft reset, vil jeg bytte til en av de eldre commitene med SHA 0db602e085a4d59cfa9393abac41ff5fd7afcb14

For å gjøre dette, kjører jeg kommandoen nedenfor. Det er nok å oppgi mer enn 6 starttegn i SHA. Full SHA er ikke nødvendig.

$ git reset --soft 0db602e085a4

Nå når jeg kjører git loggen, ser jeg at HEAD er tilbakestilt til commiten jeg spesifiserte.

$ 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

Men forskjellen her er at filene fra commiten (aa400858aab3927e79116941c715749780a59fc9), der jeg hadde lagt til 3 filer, fortsatt er i min arbeidsmappe. De har ikke blitt slettet. Dette er grunnen til at du bør bruke en soft reset i stedet for en hard reset. Det er ingen risiko for å miste filer i soft modus.

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

Git Revert

I Git brukes revert-kommandoen til å utføre en revert-operasjon, det vil si å tilbakestille noen endringer. Det ligner på reset-kommandoen, men forskjellen er at du her utfører en ny commit for å gå tilbake til en spesifikk commit. Enkelt sagt, kan vi si at git revert-kommandoen selv er en commit.

Git revert-kommandoen sletter ikke data når du utfører en revert-operasjon.

La oss si at jeg legger til 3 filer og utfører en git commit-operasjon for revert-eksempelet.

$ 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

Loggen vil vise den nye commiten.

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

Nå ønsker jeg å gå tilbake til en av mine tidligere commits, la oss si «59c86c9 new commit». Jeg kjører kommandoen nedenfor.

$ git revert 59c86c9

Dette åpner en fil, der du finner detaljene for commiten du prøver å gå tilbake til. Du kan gi den nye commiten et navn her, og deretter lagre og lukke filen.

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

Etter at du har lagret og lukket filen, er dette utdataene du får.

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

Nå, for å gjøre de nødvendige endringene, har revert, i motsetning til reset, utført en ny commit. Sjekker du loggen igjen, finner du en ny commit som følge av revert-operasjonen.

$ 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-loggen vil inneholde all historikk om commits. Hvis du vil fjerne commits fra historikken, er ikke revert et godt valg. Hvis du derimot vil opprettholde endringene i historikken, er revert den passende kommandoen i stedet for reset.

Git Rebase

I Git er rebase en måte å flytte eller kombinere commits fra en gren over på en annen gren. Som utvikler vil du sjelden jobbe direkte i mastergrenen. Du vil jobbe i din egen gren (en «funksjonsgren»). Når du har gjort noen commits i funksjonsgrenen og lagt til en funksjon, vil du flytte denne funksjonen over til mastergrenen.

Rebase kan noen ganger være litt forvirrende å forstå, fordi det ligner på en sammenslåing. Målet med både sammenslåing og rebase er å ta commits fra en feature-gren og legge dem til master-grenen eller en annen gren. Tenk deg en graf som ser slik ut:

Anta at du jobber i et team med flere utviklere. Da kan du tenke deg at dette kan bli komplisert. Du kan ha mange utviklere som jobber med forskjellige funksjonsgrener, og som har gjort en rekke endringer. Det kan bli vanskelig å holde oversikten.

Det er her rebase kommer inn. I stedet for å gjøre en git merge, vil jeg gjøre en rebase der jeg tar mine to commits fra feature-grenen og flytter dem over til master-grenen. En rebase tar alle commits fra feature-grenen og flytter dem oppå commits i master-grenen. Bak kulissene dupliserer Git funksjonsgrenen og legger den til på mastergrenen.

Dette gir deg en ren, lineær graf med alle commits på rad.

Det gjør det enkelt å spore hvilke commits som ble gjort hvor. Tenk deg at du er i et team med mange utviklere. Alle commits vil fremdeles være på rad. Dette gjør det enkelt å følge med selv om mange jobber med samme prosjekt samtidig.

La meg vise deg dette praktisk.

Slik ser mastergrenen min ut for øyeblikket. Den har 4 commits.

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

Jeg kjører kommandoen nedenfor for å opprette og bytte til en ny gren som heter feature. Denne grenen opprettes fra den andre commiten, det vil si 59c86c9.

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

Sjekker du loggen i funksjonsgrenen, ser du at den bare har 2 commits som kommer fra master (hovedlinjen).

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

Jeg lager en funksjon 1 og committer den til funksjonsgrenen.

(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

Jeg lager en funksjon til, dvs. funksjon 2, i funksjonsgrenen, og committer den.

(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

Nå, sjekker du loggen til funksjonsgrenen, ser du at den har to nye commits, som jeg gjorde ovenfor.

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

Nå vil jeg legge til disse to nye funksjonene til mastergrenen. For dette bruker jeg rebase-kommandoen. Fra funksjonsgrenen rebaser jeg mot mastergrenen. Dette vil gjøre at funksjonsgrenen baserer seg på de siste endringene.

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

Nå går jeg videre og sjekker mastergrenen.

(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)

Og til slutt, rebase mastergrenen mot funksjonsgrenen. Dette tar de to nye commitene i funksjonsgrenen og legger dem til på toppen av mastergrenen.

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

Hvis jeg nå sjekker loggen på mastergrenen, ser jeg at de to commits fra funksjonsgrenen er lagt til i mastergrenen.

(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

Dette var en gjennomgang av kommandoene reset, revert og rebase i Git.

Konklusjon

Vi har nå sett på reset, revert og rebase kommandoene i Git. Jeg håper denne guiden var nyttig. Nå vet du hvordan du kan manipulere commits etter behov ved hjelp av kommandoene nevnt i artikkelen.