-
1. Kom igång
- 1.1 Om versionshantering
- 1.2 En kort historik av Git
- 1.3 Vad är Git?
- 1.4 Kommandoraden
- 1.5 Installera Git
- 1.6 Använda Git för första gången
- 1.7 Få hjälp
- 1.8 Sammanfattning
-
2. Grunder i Git
- 2.1 Skaffa ett Git-förvar
- 2.2 Spara ändringar till förvaret
- 2.3 Visa historiken
- 2.4 Ångra saker
- 2.5 Jobba med fjärrförvar
- 2.6 Taggning
- 2.7 Git alias
- 2.8 Sammanfattning
-
3. Git förgreningar
- 3.1 Grenar i ett nötskal
- 3.2 Grundläggande förgrening och sammanslagning
- 3.3 Hantera grenar
- 3.4 Arbetsflöde med grenar
- 3.5 Fjärrgrenar
- 3.6 Grenflytt
- 3.7 Sammanfattning
-
4. Git på servern
- 4.1 Protokollen
- 4.2 Skaffa Git på en server
- 4.3 Generera din publika SSH-nyckel
- 4.4 Konvigurera servern
- 4.5 Git Daemonen
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Alternativ tillhandahållna av tredje part
- 4.10 Sammanfattning
-
5. Distributed Git
- 5.1 Distributed Workflows
- 5.2 Contributing to a Project
- 5.3 Maintaining a Project
- 5.4 Summary
-
6. GitHub
-
7. Git Tools
- 7.1 Revision Selection
- 7.2 Interactive Staging
- 7.3 Stashing and Cleaning
- 7.4 Signing Your Work
- 7.5 Searching
- 7.6 Rewriting History
- 7.7 Reset Demystified
- 7.8 Advanced Merging
- 7.9 Rerere
- 7.10 Debugging with Git
- 7.11 Submodules
- 7.12 Bundling
- 7.13 Replace
- 7.14 Credential Storage
- 7.15 Summary
-
8. Customizing Git
- 8.1 Git Configuration
- 8.2 Git Attributes
- 8.3 Git Hooks
- 8.4 An Example Git-Enforced Policy
- 8.5 Summary
-
9. Git and Other Systems
- 9.1 Git as a Client
- 9.2 Migrating to Git
- 9.3 Summary
-
10. Git Internals
- 10.1 Plumbing and Porcelain
- 10.2 Git Objects
- 10.3 Git References
- 10.4 Packfiles
- 10.5 The Refspec
- 10.6 Transfer Protocols
- 10.7 Maintenance and Data Recovery
- 10.8 Environment Variables
- 10.9 Summary
-
A1. Bilaga A: Git in Other Environments
- A1.1 Graphical Interfaces
- A1.2 Git in Visual Studio
- A1.3 Git in Eclipse
- A1.4 Git in Bash
- A1.5 Git in Zsh
- A1.6 Git in PowerShell
- A1.7 Summary
-
A2. Bilaga B: Embedding Git in your Applications
- A2.1 Command-line Git
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
A3. Bilaga C: Git Commands
- A3.1 Setup and Config
- A3.2 Getting and Creating Projects
- A3.3 Basic Snapshotting
- A3.4 Branching and Merging
- A3.5 Sharing and Updating Projects
- A3.6 Inspection and Comparison
- A3.7 Debugging
- A3.8 Patching
- A3.9 Email
- A3.10 External Systems
- A3.11 Administration
- A3.12 Plumbing Commands
3.6 Git förgreningar - Grenflytt
Grenflytt
I Git finns i huvusak två sätt att integrera ändringar från en gren in i en annan: sammanslagning (merge
) eller grenflytt (rebase
).
I detta avsnitt kokker du får lära dig vad en grenflytt är, hur man gör det och varför det är ett ganska häpnadsväckande verktyg, samt i vilka fall du inte vill använda det.
Den grundläggande grenflytten
Om du går tillbaks till ett tidigare exempel från Grundläggande sammanslagning, kan du se att du divergerade ditt arbete och gjorde versioner på två olika grenar.
Det änklaste sättet att integrera grenar är, som vi redan gått igenom, kommandot merge
.
Den genomför en trevägssammanslagning mellan de två senaste ögonblicksbilderna (C3
och C4
) och den senaste gemensamma versionen av de två grenarna (C2
) och skapar en ny ögonblicksbild (och version).
Det finns emellertid ett annat sätt: Du kan ta ändringarna som introducerades i C4
och tillämpa den på toppen av C3
.
I Git kallas detta för grenflytt (eng. rebasing).
Med kommandot rebase
kan du ta alla ändringar som sparats i en gren och spela upp dem på en annan gren.
I detta exemplet kommer du checka ut experiment
-grenen och sedan flytta grenen till master
-grenen som följer:
$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
Denna operation fungerar genom att hitta den senaste gemensamma versionen för de två grenarna (den du står på och den du skall flytta din gren till), ta reda på skillnaderna som introducerats i varje version av den gren du står på, spara dessa i temporära filer, peka om den aktuella grenen till toppen av den gren som du skall flytta din aktuella gren till, och sedan applicera ändringarna i turordning.
C4
på toppen av C3
Nu kan du gå tillbaka till master
-grenen och göra en sammanslagning via snabbspolning.
$ git checkout master
$ git merge experiment
Ögonblicksbilden som C4'
pekar på är exakt samma som den som C5
pekade på i the merge example.
Det finns ingen skillnad i slutprodukten av integrationen, men att flytta grenen gör att historiken blir renare.
Om du undersöker historiken av en flyttad gren kommer den vara linjär: det verkar som att allt arbete har skett sekvensiellt, trots att det egentligen skedde parallellt.
Ofta vill du göra detta för att säkerställa att dina versioner kan läggas till rent på en fjärrgren — kanske i ett projekt som du försöker bidra till men som du inte underhåller.
I detta fall gör du ditt arbete i en gren och sedan flyttar ditt arbet in i origin/master
när du är redo att publicera dina ändringar till huvudprojektet.
På detta vis behöver inte den som underhåller projektet göra något integrationsarbete — endast snabbspola eller lägga till ändringarna rent.
Notera att ögonblicksbilden som pekas på av den slutliga versionen, oavsett om det är den senaste av de flyttade versionerna för en grenflytt eller den slutliga versionen efter en sammanslagning är samma ögonblicksbild — det är bara historiken som skiljer. Grenflytt spelar upp ändringarna från en arbetshistorik på toppen av en annan i samma ordning de introducerades, medan sammanslagning tar ändpunkterna på varje gren och slår ihop dem.
Mer intressanta grenflyttar
Du kan också spela upp din historik på något annat än den ursprungliga basgrenen.
Ta en historik som Historik med en gren från en annan gren, till exempel.
Du gjorde en gren (server
) för att lägga till lite serverfunktionalitet till ditt projekt och skapade en ny version.
Därefter gjorde du en gren från denna för att göra motsvarande ändringar hos klienten (client
) och gjorde några nya versioner.
Slutligen gick du tillvaks till din servergren och gjorde några fler versioner.
Antag att du beslutar att du vill slå samman din klientfunktionalitet till ditt huvudspår för att frisläppa dem, men att du vill avvakta serverändringarna tills dessa är testade.
Du kan ta klietändringarna som inte är på server (C8
och C9
) och spela upp dem på din master
gren genom att använda flaggan --onto
till git rebase
:
$ git rebase --onto master server client
Detta betyder i praktiken “Ta client
grenen, ta reda på de patchar sedan den divergerade från server
grenen, och spela upp dem på klient
delen som om de vore baserade direkt från master
grenen istället.”
Det är lite komplext, men resultatet är rätt häftigt.
Nu kan du snabbspola din master
gren (se Snabbspola din master-gren till att inkludera klientgrenens ändringar):
$ git checkout master
$ git merge client
Säg att du beslutar att dra in din servergren också.
Du kan spela upp servergrenen på master
grenen utan att behöva checka ut den först genom att köra git rebase <basgren> <stickspår>
— vilket checkar ut stickspåret (server
i detta fall) för dig och spelar up den på basgrenen (master
):
$ git rebase master server
Detta spelar upp ditt arbete i server
ovan på ditt arbete i master
, som synes i Flytta din servergren til toppen av din mastergren.
Därefter kan du snabbspola din basgren (master
):
$ git checkout master
$ git merge server
Du kan ta bort grenarna client
och server
eftersom allt arbete är integrerat, vilket ger dig en historik för denna process likt Slutlig versionshistorik:
$ git branch -d client
$ git branch -d server
Farorna med grenflyttar
Ahh, lyckan med grenflytt är inte helt utan nackdelar, vilka kan sammanfattas i en mening:
Flytta inte versioner som existerar utanför ditt lokala repo som andra kan ha baserat sitt arbete på.
Följer du det tipset så kommer allt gå bra. Om inte, kommer folk hata dig och du kommer att hånas av dina vänner och familj.
När du flyttar om saker överger du existerande versioner och skapar nya som är lika, men annorlunda.
Om du publicerar versioner någonstans och andra hämtar dem och baserar arbete på dem och du sedan skriver om historiken med git rebase
och publicerar dessa ändringarna igen, kommer dina medarbetare att behöva återintegrera sitt arbete och saker kommer bli krånligt när du försöker integrera deras ändringar i dina.
Låt oss ta ett exempel på hur det kan uppstå problem om du skriver om arbete som du gjort publikt. Antag att du klonar från en central server och sedan gör lite arbete på det. Din versionshistorik ser ut såhär:
Nu gör någon annan mer arbete som inkluderar en sammanslagning och publicerar det arbetet till den centrala servern. Du hämtar det och slår ihop fjärrgrenen in i ditt arbete vilket gör att din versionshistorik ser ut ungefär såhär:
Sedan bestämmer sig personen som publicerade ändringarna att gå tillbaks och skriva om sin historik istället; de gör git push --force
för att skriva över den historik som finns på servern.
Du hämtar sedan från den server, och får hem de nya versionerna.
Nu sitter ni båda i skiten.
Om du gör git pull
kommer du skapa en sammanslagningsversion som inkluderar båda versionstidslinjerna, och ditt repo kommer se ut såhär:
Om du kör git log
när din historik ser ut såhär kommer du se två versioner som har samma författare, datum och meddelande, vilket kommer vara förvirrande.
Vidare, om du publicerar denna historik tillbaks till servern, kommer du återintroducera alla de tidigare omskrivna versionerna vilket kan förvirra andra också.
Man kan vara ganska säker på att den andra utvecklaren inte vill att C4
och C6
skall vara i historiken; det är därför de skrev om historiken från början.
Flytta en gren när du flyttar en gren
Om du däremot finner att du är i en liknande sitiation, så har Git lite ytterligare magi som kan komma väl till pass. Om någon i ditt team trycker ut en ändring som skriver över arbete som du baserar arbete på, blir din utmaning att ta reda på vad som är ditt och vad de har skrivit om.
Det faller sig så att utöver till versionens SHA-1 checksimma, beräknar Git också en checksumma baserat på just den patch som introducerades med versionen. Denna kallas för “patch-id”.
Om du hämtar hem arbete som var omskrivet och gör en egen omskrivning på toppen av versionerna från din kollega, kan Git ofta lista ut vad som är unikt ditt och och applicera dina ändringar ovanpå den nya grenen.
Till exempel, i föregående scenario, om du istället för att slå ihop ändringarna vid Någon publicerar omskriven historik, och överger versioner på vilka du baserat arbete och kör git rebase teamone/master
, kommer Git att:
-
Ta reda på vilket arbete som är unikt för vår gren (C2, C3, C4, C6, C7)
-
Ta reda på vilka som inte är sammanslagningsversioner (C2, C3, C4)
-
Ta reda på vad som inte har skrivits om i målgrenen (bara C2 och C3, eftersom C4 är samma patch som C4')
-
Applicera de ändringarna ovanpå
teamone/master
Så istället för resultatet i Du integrerar samma arbete igen i en ny sammanslagningsversion kommer vi få ett slutligt resultat liknande Grenflytt till toppen av en tvingande publicering av omskriven historik.
Detta fungerar bara om C4 och C4' som din kollega gjort är näst intill samma patch. Annars kommer Git inte kunna avgöra att de är duplikat och kommer lägga till ytterligare en C4-lik patch (som förmodligen inte kommer gå att applicera rent, eftersom ändringarna bitvis redan är på plats).
Du kan också förenklad detta genom att köra git pull --rebase
istället för en vanlig git pull
.
Eller så kan du göra det manuellt genom git fetch
följt av git rebase teamone/master
i detta fallet.
Använder du git pull
och vill göra --rebase
till normalfallet, kan du sätta konfigureringsparametern pull.rebase
med något liknande git config --global pull.rebase true
.
Om du nägonsin skriver om historik som bara finns lokalt på din dator kommer du vara helt säker. Om du skriver om historik som är publicerade, men som ingen annan baserat versioner på, kommer du också vara helt säker. Om du skriver om versionshistorik som redan har publicerats publikt, och som folk har baserat arbete på, kommer du hamna i frustrerande trubbel och hånas av dina teammedlemmar.
Om du eller en kollega anser det vara nödvändigt vid något tillfälle, se till att alla vet om att de skall köra git pull --rebase
för att göra den efterföljande pinan något lättare.
Omskrivning vs. Sammanslagning
Nu när du sett hur omskrivning och sammanslagning fungerar, kanske du undrar vilken som är bäst. Innan vi kan svara på det, låt oss ta ett steg tillbaka och prata om vad historik betyder.
En infallsvinkel på det är att dit repos versionshistorik är en beskrivning över vad som faktiskt hände. Det är ett historiskt dokument, värdefull i sig själv, och skall inte manipuleras. Med denna vinkel är ändring av versionshistoriken närmast blasfemi; du ljuger om vad som faktisk skedde. Vad gör det om det är en stökig historik av sammanslagningsversioner? Det var så det hände, och repot skall bevara det för eftervärlden.
En motstående infallsvinkel är att versionshistoriken är berättelsen av hur ditt projekt skapades. Du publicerar inte första utkastet av en bok, och manualen för hur du underhåller din mjukvara förtjänar noggrann redigering. Detta är lägret som använder verktyg som omskrivning och filter-grenar för att berätta historien som bäst lämpar sig för framtida läsare.
Nu till frågan huruvida sammanslagning eller omskrivning är bättre: förhoppningsvis inser du att det inte är så enkelt. Git är ett kraftfullt verktyg och tillåter dig att göra många saker med din historik, men alla team och alla projekt är olika. Nu när du vet hur båda dessa verktyg fungerar, är det upp till dig att avgöra vilken metod som är bäst lämpad i din specifika situation.
I allmänhet, för att få det bästa från två världar, är att skriva om lokala ändringar du gjort men ännu inte delat innan du publicerar dem i syfte att rensa upp din historik, men att aldrig skriva om historik du publicerat nånstans.