Git
Chapters ▾ 2nd Edition

2.2 Grunder i Git - Spara ändringar till förvaret

Spara ändringar till förvaret

Nu skall du ha ett äkta Git-förvar framför dig på din lokala maskin, och en utcheckad eller arbetskopia av alla dess filer. Vanligtvis vill du nu göra ändringar och spara ögonblicksbilder av dessa ändringarna till ditt förvar varje gång projektet når ett tillstånd du vill spara.

Kom ihåg att varje fil i din arbetskatalog kan vara i en av två tillstånd: spårad eller ospårad. Spårade filer är filer som fanns med i den tidigare ögonblicksbilden; de kan vara omodifierade, modifierade eller förberedda. I korthet kan man säga att spårade filer är de filer som Git känner till.

Ospårade filer är allt annat — filer i din arbetskatalog som inte var med i din tidigare ögonblicksbild och som inte ligger på prepareringsytan. När du först klonar ett förvar kommer alla filer att vara spårade och omodifierade, eftersom Git precis checkat ut dem utan att du hunnit ändra något.

När du ändrar i filer kommer Git markera dem som modifierade, eftersom du har ändrat dem sedan din föregående sparade version. Under arbetets gång väljer du att frysa de modifierade filerna och sedan sparar du dessa ändringarna, och sedan börjar det om på nytt.

Livscykeln för statusen hos dina filer.
Figur 8. Livscykeln för statusen hos dina filer.

Kontrollera status på dina filer

Det huvudsakliga verktyget som används för att avgöra vilka filer är i vilket steg är kommandot git status. Om du kör det kommando direkt efter att ha klonat ett förvar, kommer du se något liknande detta:

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean

Detta betyder att du har en ren arbetskatalog, vilket i andra termer betyder att inga av dina spårade filer är modifierade. Git ser heller inte några ospårade filer, då de i sådant fall skulle listas här. Slutligen talar kommandot om vilken gren du är på och informerar den om att den inte har divergerat gentemot samma gren på servern. För nu kan du anta att grenen alltid är “master”, vilket är standard; du behöver inte bry dig om detta ännu. Git förgreningar kommer gå igenom grenar och referenser i detalj.

Låt oss säga att du lägger till en ny fil i ditt projekt, en simpel README fil. Om filen inte existerar sedan tidigare och du kör git status, kommer du se din ospårade fil:

$ echo 'My Project' > README
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Untracked files:
  (use "git add <file>..." to include in what will be committed)

    README

nothing added to commit but untracked files present (use "git add" to track)

Som synes är din nya fil README ospårad eftersom den ligger under rubriken “Untracked files” (Ospårade filer) i din statusutskrift. Ospårad betyder egentligen bara att Git ser en fil som inte finns med i din tidigare ögonblicksbild (version); Git kommer inte inkludera den i kommande ögonblicksbilder om du inte uttryckligen talar om för Git att du vill göra det. Anledningen är för att du inte oavsiktligen skall börja inkludera generarde binärfiler eller andra filer som du inte har för avsikt att inkludera. Du vill ju såklart inkludera filen README, så då gör vi det.

Spåra nya filer

För att börja spåra en ny fil använder du kommandot git add. För att börja spåra README kör du detta kommando:

$ git add README

Om du kör ditt statuskommando igen, kommer du se att din README-fil nu är spårad och förberedd för att ingå i nästa version:

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   README

Att filen är förberedd vet du eftersom den finns under rubriken “Changes to be committed” (Ändringar som kommer att sparas). Om du låser ändringen nu kommer filen som den såg ut när du körde kommandot git add att komma med i den kommande ögonblicksbilden. Dra dig till minnes när du förut körde git init så körde du sedan git add <filer> — det var för att börja spåra filer i ditt förvar. Kommandot git add tar en sökväg till antingen en fil eller en katalog; om det är en katalog kommer alla filer i katalogen läggas till rekursivt.

Förbereda modifierade filer

Låt oss ändra en fil som var spårad sedan tidigare. Om du ändrar en tidigare spårad fil kallad CONTRIBUTING.md och sedan kör kommandot git status igen kommer du få något som liknar detta:

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   README

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   CONTRIBUTING.md

Filen CONTRIBUTING.md visas under avsnittet “Changes not staged for commit” (Ändringar som inte är markerade att ingå i nästa version) — vilket betyder att en spårad fil har modifierats i arbetskatalogen men ännu inte valts att ingå i nästa version. För att förbereda den, kör du kommandot git add. git add har flera användningsområden — du använder det för att börja spåra nya filer, för att förbereda filer, och för att göra andra saker som att markera sammanslagningskonflikter som lösta. Det kan vara lättare att tänka på det som ett “lägg till exakt detta innehåll i nästa version” snarare än “lägg till denna filen till projektet”. Låt os köra git add nu för att förberade filen CONTRIBUTING.md och sedan köra `git status`igen:

$ git add CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   README
    modified:   CONTRIBUTING.md

Båda filer är förberedda och kommer att ingå i din nästa version. Anta att du i detta läget kommer på att du vill göra en liten ändring i CONTRIBUTING.md innan du sparar en version med den. Du öppnar filen igen och gör ändringen, och är nu redo för att göra en förbindning. Låt oss först köra git status en gång till:

$ vim CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   README
    modified:   CONTRIBUTING.md

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   CONTRIBUTING.md

Vad tusan betyder det? Nu är filen CONTRIBUTING.md listad som både förberedd för nästa version och modifierad för att inte ingå i nästa version. Hur är det möjligt? Det visar sig att Git förbereder filen så som den ser ut just när du kör kommandot git add. Om du sparar en version nu, kommer den version av CONTRIBUTING.md som den var då du senast körde kommandot git add att ingå i versionen, inte så som den ser ut i din arbetskatalog när du kör git commit. Om du modifierar en fil efter att du kör git add, så måste du köra git add igen för att välja den senaste versionen av filen:

$ git add CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    new file:   README
    modified:   CONTRIBUTING.md

Kort status

Medan utskriften från git status är ganska omfattande är den också ganska långrandig. Git har också en kort statusflagga så att du kan se dina ändringar mer kompakt. Om du kör kommandot git status -s eller git status --short kommer du få en mer kompakt utskrift:

$ git status -s
 M README
MM Rakefile
A  lib/git.rb
M  lib/simplegit.rb
?? LICENSE.txt

Nya filer som inte spårats har ?? till vänster om namnet, nya filer som lagts till prepareringsytan har ett A, modifierade filer har ett M, och så vidare. Det är två kolumner till vänster om filnamnet i utskriften — den vänstra kolumnen indikerar statusen hos prepareringsytan (vad som kommer ingå i nästa version), och den högra kolumnen indikerar status för arbetsträdet. I det tidigare exemplet är filen README modifierad i arbetsträdet men inte förberedd att ingå i nästa version, medan filen lib/simplegit.rb är modifierad och förberedd att ingå i nästa version. Filen Rakefile var modifierad, förberedd för nästa version och sedan modifierad igen, så den innehåller ändringar som är både förberedda och oförberedda.

Ignorera filer

Ofta har du en uppsättning filer som du inte vill att Git automatiskt lägger till eller ens visar som ospårade. Till dessa hör i regel automatgenerarde filer såsom loggfiler, eller filer som skapas av ditt byggsystem. I sådana fall kan du skapa en fil med namnet .gitignore som listar de mönster filerna har. Här är ett exempel på en .gitignore-fil:

$ cat .gitignore
*.[oa]
*~

Den första raden talar om för Git att ignorera alla filer som slutar på “.o” eller “.a” — objekt- och arkivfiler som kan skapas när du bygger din kod. Den andra raden talar om för Git att ingorera alla filer vars namn slutar med tilde (~), som används av många texteditorer såsom Emacs för att markera temporära filer. Du kan också inkludera log-, tmp- och pid-kataloger, automatiskt genererad dokumentation, och så vidare. Att skapa en .gitignore-fil för ditt nya förvar innan du börjar arbeta med det är generellt en bra idé så att du inte oavsiktigt versionshanterar filer som du inte vill ha i ditt Git-förvar.

De mönster du kan använda i .gitignore-filen måste följa dessa regler:

  • Blanka rader eller rader som börjar med # ignoreras.

  • Vanliga glob-mönster fungerar, och kommer att appliceras rekursivt genom hela arbetsträdet.

  • Använd ett inledande snedstreck (/) i ett mönster för att undvika rekursivitet.

  • Avsluta ett mönster med ett snedstreck (/) för att specificera en katalog.

  • Genom att inleda med ett utropstecken(!) får mönstret en logiskt motsatt betydelse.

Glob-mönster är simplifierade reguljära uttryck som skal använder. En asterisk (*) matchar en eller flera tecken; [abc] matchar en av tecknen inom hakparentesen (i detta fallet a, b, eller c), ett frågetecken (?) matchar ett enskilt tecken, och hakparenteser som omsluter tecken separerat med ett bindestreck ([0-9]) matchar ett tecken mellan dem (i detta fallet från och med 0 till och med 9). Du kan också använda två asterisker för att matcha nästlade kataloger; a/**/z skulle således matcha a/z, a/b/z, a/b/c/z, och så vidare.

Här är ytterligare ett exempel på en .gitignore-fil:

# ignorera alla .a filer
*.a

# men spåra lib.a, även om du ignorerar .a filer enligt regeln ovan
!lib.a

# ignorera enbart TODO-filen i den aktuella katalogen, inte underkatalog/TODO.
/TODO

# ignorera alla filer i kataloger som heter build
build/

# ignorera doc/notes.txt, men inte doc/server/arch.txt
doc/*.txt

# ignorera alla .pdf-filer i doc-katalogen och någon av dess underkataloger
doc/**/*.pdf
Tips

GitHub underhåller en ganska omfattande lista av bra exempel på .gitignore-filer för dussintals projekt och språk på https://github.com/github/gitignore om du vill ha en till ditt projekt.

Notera

I det enklaste fallet kan ett förvar ha en enda .gitignore-fil i sin rotkatalog, vilken appliceras rekursivt i hela förvaret. Det är emellertid möjligt att ha flera .gitignore-filer i underkataloger. Reglerna i de nästlade .gitignore-filerna appliceras endast på filer i och under den katalåg vari filen själv finns. (Linuxkärnans förvar har 206 .gitignore-filer.)

Det ligger utanför denna boks omfattning att gå in i detalj på användning av flera .gitignore-filer; se man gitignore för detaljer.

Visa dina förberedda och oförberedda ändringar

Om kommandot git status är för vagt för dig — du vill veta exakt vad du ändrade, inte bara vilka filer som ändrats — kan du använda kommandot git diff. Vi kommer att gå igenom git diff i mer detalj senare, men du kommer förmodligen oftast att använda det för att svara på följande frågor: Vad har du ändrat men ännu inte förberett? Och vad har du förberett och som du kommer att spara i nästa version? Trots att git status svarar på frågorna väldigt generellt genom att lista filnamnen, kommer `git diff`visa dig exakt vilka rader som lagts till och tagits bort — patchen, som det också kallas.

Låt oss säga att du editerar och förbereder README-filen igen och sedan ändrar CONTRIBUTING.md-filen utan att förbereda den. Om du kör ditt git status-kommando, kommer du återigen att se något liknande detta:

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   README

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   CONTRIBUTING.md

För att se vad du ändrat men ännu inte förberett, skriv git diff utan några andra argument:

$ git diff
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8ebb991..643e24f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -65,7 +65,8 @@ branch directly, things can get messy.
 Please include a nice description of your changes when you submit your PR;
 if we have to read the whole diff to figure out why you're contributing
 in the first place, you're less likely to get feedback and have your change
-merged in.
+merged in. Also, split your changes into comprehensive chunks if your patch is
+longer than a dozen lines.

 If you are starting to work on a particular area, feel free to submit a PR
 that highlights your work in progress (and note in the PR title that it's

Kommandot jämför det som finns i din arbetskatalog med vad som finns på din prepareringsyta. Resultatet ger dig ändrignarna som du gjort som du ännu inte förberett.

Om du vill se vad du förberett och som kommer att ingå i din nästa version, kan du använda git diff --staged. Detta kommando jämför dina ändringar som kommer ingå i din nästa version med din senast sparade version:

$ git diff --staged
diff --git a/README b/README
new file mode 100644
index 0000000..03902a1
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+My Project

Viktigt att notera är att git diff i sig självt inte visar alla ändringar genomförda sedan din senaste version — bara ändringar som ännu inte kommer ingå i nästa version. Om du har förberett alla dina ändringar att ingå i nästa version, kommer git diff inte att ge någon utskrift.

Ytterligare ett exempel, om du förbereder filen CONTRIBUTING.md och sedan ändrar den, kan du använda git diff för att se ändringarna i filen som är förberedda och ändringarna som är oförberedda. Om vår miljö ser ut såhär:

$ git add CONTRIBUTING.md
$ echo '# test line' >> CONTRIBUTING.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   CONTRIBUTING.md

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   CONTRIBUTING.md

Nu kan du använda git diff för att se vad som fortfarande är oförberett:

$ git diff
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 643e24f..87f08c8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -119,3 +119,4 @@ at the
 ## Starter Projects

 See our [projects list](https://github.com/libgit2/libgit2/blob/development/PROJECTS.md).
+# test line

och git diff --cached för att se vad du förberett hittils (--staged och --cached är synonymer):

$ git diff --cached
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 8ebb991..643e24f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -65,7 +65,8 @@ branch directly, things can get messy.
 Please include a nice description of your changes when you submit your PR;
 if we have to read the whole diff to figure out why you're contributing
 in the first place, you're less likely to get feedback and have your change
-merged in.
+merged in. Also, split your changes into comprehensive chunks if your patch is
+longer than a dozen lines.

 If you are starting to work on a particular area, feel free to submit a PR
 that highlights your work in progress (and note in the PR title that it's
Notera
Git Diff i ett externt verktyg

Vi kommer att fortsätta använda git diff på olika sätt under resten av boken. Det finns ytterligare ett sätt att titta på dessa skillnader om du föredrar ett grafiskt eller externt diff-visningsprogram istället. Om du kör git difftool iställlet för git diff, kan du visa vilken som helst av dessa skillnader i program som emerge, vimdiff och många fler (inklusive kommersiella produkter). Kör git difftool --tool-help för att se vad som finns tillgängligt på ditt system.

Spara dina ändringar

När din prepareringsyta innehåller det du vill, kan du låsa dina ändringar. Kom ihåg att allt som fortfarande är oförberett — alla filer du har skapat eller modifierat sedan du körde git add sedan du editerat dem — inte kommer att ingå i denna version. De kommer fortfarande vara modifierade filer på din hårddisk. I detta fallet, anta att när du sist körde git status så såg du att allt var förberett och du är redo att låsa dina ändringar. Det enklaste sättet att spara en version på är att skriva git commit:

$ git commit

När du gör det startas din editor. (Det är den editor som är satt i ditt skals miljövariabel EDITOR — vanligtvis vim eller emacs, även om du kan konfigurera den till vilken du vill genom att använda kommandot git config --global core.editor som du såg i Kom igång).

Editorn visar följande text (detta exempel är en Vim-skärm):

# 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 up-to-date with 'origin/master'.
#
# Changes to be committed:
#	new file:   README
#	modified:   CONTRIBUTING.md
#
~
~
~
".git/COMMIT_EDITMSG" 9L, 283C

Du kan se att standardmeddelandet innehåller den senaste utskriften från kommandot git status bortkommenterat och en tom rad högst upp. Du kan ta bort dessa kommentarer och skriva in ditt versionsmeddelande, eller så lämnar du dem kvar för att hjälpa dig komma ihåg vad du håller på att spara. (För en uttryckligare påminnelse av vad du modifierat, så kan du ge flaggan -v till git commit. Gör du det, får du också diffen av din ändring i editorn så du kan se exakt vilka ändringar du sparar.) När du avslutar editor, skapar Git en version med ditt meddelande (med kommentarer och diffar borttagna).

Alterantivt, så kan du ange ditt versionsmeddelande samtidigt som du ger kommandot commit, genom att specificera det efter flaggan -m, såhär:

$ git commit -m "Story 182: Fix benchmarks for speed"
[master 463dc4f] Story 182: Fix benchmarks for speed
 2 files changed, 2 insertions(+)
 create mode 100644 README

Nu har du skapat din första version! Du kan se att versionen har gett dig lite utskrifter om sig själv: vilken gren du sparade på (master), vilken SHA-1 checksumma den har (463dc4f), hur många filer som ändrats, samt statistik om antal rader som lagts till och tagits bort i versionen.

Kom ihåg att versionen sparar ögonblicksbilden som du satt upp i din prepareringsyta. Allt som du inte förberedde är fortfarande kvar och är modifierat; du kan göra ytterligare en förbidning för att lägga det till din historik. Varje gång du sparar en version så sparar du en ögonblicksbild av ditt projekt som du kan återgå till eller jämföra med vid ett senare tillfälle.

Hoppa över prepareringsytan

Även om det kan vara fantastiskt användbart att skapa versioner precis som du vill ha dem är prepareringsytan ibland lite för komplex för vad du behöver i ditt arbetsflöde. Om du vill hoppa över den, så erbjuder Git en enkel genväg. Lägger du till växeln -a till kommandot git commit så kommer varje fil som är spårad av Git att automatiskt läggas till prepareringsytan innan en version sparas, så att du kan hoppa över delen med git add:

$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   CONTRIBUTING.md

no changes added to commit (use "git add" and/or "git commit -a")
$ git commit -a -m 'added new benchmarks'
[master 83e38c7] added new benchmarks
 1 file changed, 5 insertions(+), 0 deletions(-)

Lägg märke till hur du inte behöver köra git add på filen CONTRIBUTING.md innan du skapar din version i detta fallet. Det är eftersom flaggan -a inkluderar alla ändrade filer. Detta är bekvämt, men var försiktig; ibland kommer flaggan göra så att du inkluderar oönskade ändringar.

Ta bort filer

För att ta bort filer från Git måste du ta bort dem från dina spårade filer (eller mer korrekt, ta bort dem från din prepareringsyta) och sedan spara en version. Kommandot git rm gör just deft, och tar även bort filen från din arbetskatalog så att du inte ser den som en ospårad fil nästa gång.

Om du bara tar bort filen från din arbetskatalog kommer den att visas under rubriken “Changes not staged for commit” (det vill säga, oförberedd) i utskriften från git status:

$ rm PROJECTS.md
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        deleted:    PROJECTS.md

no changes added to commit (use "git add" and/or "git commit -a")

Om du sedan kör git rm, kommer den att förbereda filen för borttagning:

$ git rm PROJECTS.md
rm 'PROJECTS.md'
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    deleted:    PROJECTS.md

Nästa gång du sparar en version kommer filen vara borta och inte längre spårad. Om du modifierar filen eller redan har lagt den till din prepareringsyta, måste du tvinga fram borttagning med flaggan -f. Detta är av säkerhetsskäl för att förhindra oavsiktlig borttagning av data som inte har sparats i en ögonblicksbild och som därmed inte kan återskapas av Git.

Ytterligare en användbar sak du kan tänkas vilja göra är att behålla filen i din arbetskatalog men ta bort den från din prepareringsyta. Med andra ord kan du vilja ha kvar filen på din hårddisk men inte att Git skall spåra den något mer. Detta är speciellt användbart om du glömde lägga till något till din .gitignore-fil och oavsiktigt förberedde filen, som en stor loggfil eller ett gäng kompilerade .a-filer. För att göra detta, använd växeln --cached:

$ git rm --cached README

Du kan ange filer, kataloger, och filnamnsmönster till kommandot git rm. Det betyder att du kan göra saker som:

$ git rm log/\*.log

Notera det omvända snedstrecket (\) framför *. Detta är nödvändigt eftersom at Git gör sin egna filnamnsexpansion utöver ditt skals filnamnsexpansion. Detta kommandot tar bort alla filer som har filändelsen .log i katalogen log/. Eller, så kan du göra något liknande detta:

$ git rm \*~

Detta kommandot tar bort alla filer som slutar med ~.

Flytta filer

Olikt många andra versionshanteringssystem hatnerar inte Git uttryckligen filförflyttningar. Om du byter namn på en fil i Git så sparas ingen metadata i Git som berättar att du ändrat namnet på filen. Dock är Git ganska sofistikerat när det gäller att ta reda på det — vi kommer att gå igenom det inom kort.

Det kan tyckas lite förvirrande att Git då har ett flyttkommando, mv. Om du vill byta namn på en fil i Git kan du göra något i stil med:

$ git mv file_from file_to

och det funkar bra. Faktum är att om du kör något liknande det och kollar på statusen så kommer du se att Git antar att det rör sig om en namnändring:

$ git mv README.md README
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    renamed:    README.md -> README
Det är dock samma sak som att göra något i stil med detta
$ mv README.md README
$ git rm README.md
$ git add README

Git räknar ut att det är en filnamnsändring implicit, så det spelar ingen roll om du byter namn på en fil på det ena eller andra sättet. Den engentliga skillnaden är att git mv är ett kommando istället för tre — det är en bekvämlighetsfunktion. Än viktigare är att du kan använda vilket verktyg som helst för att döpa om filen och ta i håll med lägga till/ta bort senare, innan du sparar din version.

scroll-to-top