Git Reset vs Revert vs Rebase

I den här genomgången utforskar vi olika metoder för att hantera och manipulera commits i Git.

Som utvecklare har du förmodligen befunnit dig i situationer där du behövde återgå till ett tidigare tillstånd i din kod, men inte varit säker på hur du ska göra. Du kanske också känner till Git-kommandona som `reset`, `revert` och `rebase`, men är osäker på vad som skiljer dem åt. Nu ska vi gå igenom dessa kommandon och reda ut hur de fungerar.

Git Reset

Kommandot `git reset` är mångsidigt och kan användas för att ångra ändringar på olika sätt.

Tänk på `git reset` som en typ av ”återställningsknapp”. Med det kan du navigera tillbaka till tidigare commits. Det finns tre olika lägen för `git reset`: `–soft`, `–mixed` och `–hard`. Standardläget är `–mixed`. För att förstå hur `git reset` fungerar, måste vi först känna till tre centrala delar av Git: HEAD, staging area (index) och arbetsmappen.

Arbetsmappen är din lokala mapp där du arbetar med filer. Du kan använda kommandot `git status` för att se vilka filer och mappar som finns där.

Staging area, eller index, är en mellanlagring där Git håller reda på filändringar innan de sparas permanent i en commit. Du lägger till filer i staging area med `git add ”filnamn”`. När du kör `git status` kan du se vilka filer som finns i staging area.

HEAD i Git representerar den aktuella grenens senaste commit. Den fungerar som en referens till commits. När du byter gren flyttas HEAD till den nya grenens senaste commit.

Nu ska vi titta på hur `git reset` fungerar i lägena `hard`, `soft` och `mixed`. I `hard`-läget flyttas HEAD till den angivna commit, arbetsmappen uppdateras med filerna från den commit, och staging area återställs. I `soft`-läget flyttas endast HEAD till den angivna commit, medan alla filändringar och mellanlagrade ändringar behålls. I `mixed`-läget, som är standard, återställs både HEAD och staging area.

Git Reset Hard

Syftet med `git reset –hard` är att flytta HEAD till en viss commit. Alla commits som gjorts efter den angivna commiten tas bort. Detta kommando ändrar commit-historiken och pekar om den till den commit du anger.

I det här exemplet lägger vi till tre nya filer, commit dem och gör sedan en hård återställning.

Kommandot nedan visar att det inte finns några commit att göra för tillfället.

$ 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

Nu skapar vi 3 filer och lägger till lite innehåll i dem.

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

Vi lägger till filerna i vårt arkiv.

$ git add file*

Nu ser vi att filerna finns i staging area.

$ 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

Innan jag commit visar jag att jag har 3 commits i Git-loggen.

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

Nu commit vi de nya filerna.

$ 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

Vi ser att de nya filerna har lagts till med `ls-files`.

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

Med loggen ser vi att HEAD pekar på den senaste commiten, och att vi nu har 4 commits.

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

Om vi tar bort `file1.txt` manuellt och gör en status, ser vi att ändringarna inte är staging-förberedda.

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

Nu kör vi kommandot för hård återställning.

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

Status visar att det inte finns några ändringar att commit. Filen som vi tog bort har återskapats, eftersom vi inte committade ändringen innan återställningen.

$ 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

Loggen visar nu att den senaste commiten är den där vi lade till tre filer.

$ 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

`git reset –hard` pekar om HEAD och uppdaterar både arbetsmappen och staging area. Nu kör vi det igen, men återställer till föregående commit med `HEAD^`.

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

Vi ser att HEAD har flyttats till `0db602e` från `d69950b`.

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

Loggen visar att `d69950b` är borta och att HEAD pekar på `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

`ls-files` visar att `file1.txt`, `file2.txt` och `file3.txt` inte finns i arkivet längre, eftersom de togs bort med hård återställning.

$ git ls-files
demo
dummyfile
newfile

Git Soft Reset

Nu visar vi ett exempel på `git reset –soft`. Vi lägger till de tre filerna igen, som vi gjort tidigare, och gör en commit. Loggen ser ut som nedan. ”mjuk återställning” är vår senaste commit, och HEAD pekar på den.

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

Detaljerna för commiten syns med det här kommandot.

$ 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

Nu ska vi göra en mjuk återställning till den commit med SHA `0db602e085a4d59cfa9393abac41ff5fd7afcb14`.

Kommandot nedan gör det. Du behöver minst 6 inledande tecken från SHA-värdet.

$ git reset --soft 0db602e085a4

Nu visar `git log` att HEAD har flyttats till den angivna commiten.

$ 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

Filerna från commiten `aa400858aab3927e79116941c715749780a59fc9`, där vi lade till tre filer, finns kvar i arbetsmappen. De har inte tagits bort. Det är därför det är säkrare att använda en mjuk återställning än en hård, om du inte vill förlora filer.

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

Git Revert

Kommandot `git revert` används för att ”återställa” ändringar. Till skillnad från `reset`, skapar `revert` en ny commit för att ångra en tidigare commit. `git revert` tar alltså inte bort data under processen.

Låt oss lägga till 3 filer och committa dem för att visa ett revert-exempel.

$ 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 visar nu den nya commiten.

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

Nu ska vi återställa till den commit som heter ’59c86c9 new commit’. Vi använder kommandot nedan.

$ git revert 59c86c9

En textredigerare öppnas med information om den commit du försöker återställa. Du kan namnge den nya commiten här, spara och stänga 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

När du har sparat och stängt filen, kommer det här att vara resultatet.

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

`revert` har skapat en ny commit med åtgärden istället för att ta bort en tidigare commit. Loggen visar att det har skapats en ny commit.

$ 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 behåller historiken över alla commits. Om du vill ta bort commits från historiken är `revert` inte rätt val, men om du vill behålla commit-historiken är `revert` lämpligare än `reset`.

Git Rebase

Med `git rebase` kan du flytta eller kombinera commits från en gren till en annan. Som utvecklare bör du inte göra dina ändringar direkt i master-grenen. Istället skapar du en egen gren (en ”funktionsgren”). När du har en del commits med tillagda funktioner i din gren, kan du flytta dem till huvudgrenen.

`rebase` kan ibland vara svårt att förstå, eftersom den liknar `merge`. Båda har målet att flytta commits från en feature-gren till en master-gren eller annan gren. Tänk dig att du har en sådan här graf:

Tänk dig att du jobbar i ett team. Då kan du föreställa dig att det kan bli ganska komplext med flera utvecklare som jobbar på olika feature-grenar och sammanfogar flera ändringar. Det blir svårt att hålla reda på alla ändringar.

Det är här `rebase` är bra. Istället för att göra en `git merge`, ska vi göra en rebase, där vi vill flytta våra två feature-gren-commits till master-grenen. En rebase tar alla dina commits från feature-grenen och flyttar dem till toppen av master-grenens commits. Git gör en kopia av feature-grenen bakom kulisserna och lägger commits till master-grenen.

Detta ger dig en ren och rak linje av commits.

Det gör det enkelt att spåra vilka commits som hör till var. Om du jobbar i ett team, kommer alla commits fortfarande att finnas i en linje. Det gör det lättare att följa med även om många jobbar på samma projekt samtidigt.

Nu ska vi visa det i praktiken.

Så här ser min master-gren ut för tillfället. 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

Vi skapar en ny gren som heter ”feature”, och den kommer att skapas från den andra commiten, d.v.s. `59c86c9`.

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

Loggen i feature-grenen visar att den endast har de 2 commits som kommer från mastern.

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

Vi skapar feature 1 och committar den till feature-grenen.

(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

Vi skapar en till feature, feature 2, och committar 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

Loggen visar att feature-grenen har två nya commits.

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

Nu ska vi lägga till dessa två nya features till master-grenen. Vi använder `rebase`. Från feature-grenen rebasar vi till master. Detta kommer att anpassa feature-grenen mot de senaste ändringarna.

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

Nu ska vi gå vidare och kolla in master-grenen.

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

Och slutligen rebasar vi master-grenen mot feature-grenen. Detta lägger de två nya commits från feature-grenen ovanpå master-grenen.

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

Loggen visar nu att de två commits från feature-grenen har lagts till i master-grenen.

(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

Detta var en genomgång av kommandona `reset`, `revert` och `rebase` i Git.

Sammanfattning

Det här var en genomgång av `reset`, `revert` och `rebase` i Git. Vi hoppas att den här steg-för-steg-guiden var användbar. Nu kan du hantera dina commits på olika sätt genom att använda kommandona som nämns i artikeln.