DAT055 - Objektorienterade appikationer Introduktion till Git Anton Ekberg ekberga@student.chalmers.se Version 2 Innehåll 1 Introduktion 1 1.1 Terminalen i Linux....................................... 1 1.2 Installera Git.......................................... 2 1.3 Hosting för repos........................................ 3 1.4 Konfigurera Git......................................... 3 2 Övningar 3 2.1 Skapa lokala repon och incheckning av ändringar...................... 4 2.2 Gemensamma repon...................................... 6 2.3 Konflikter vid ihopslagning av ändringar........................... 8 2.4 Korrigera en gjord commit................................... 9 2.5 Stuva undan ändringar..................................... 10 2.6 Branching............................................ 10 2.7 Git tillsammans med en IDE................................. 11 1 Introduktion Denna övning är avsedd att ge en praktisk inblick till versionshanteringssystemet Git. Övningen utgår från orginal klienten till git som är terminalbaserad. Det är rekommenderat att använda denna klient för projekten i kursen även om det finns grafiska alternativ, eftersom dessa verkar närmast magiskt och det kan vara svårt att förstå vad man gör och varför det blir fel. För en teoretisk genomgång av Git rekommenderas föreläsningsanteckningarna 1 och dokumentationen för Git 2. 1.1 Terminalen i Linux Då denna övning är baserad i en linuxmiljö innefattar detta avsnitt en snabb genomgång av de vanligaste kommandon som används i Linux. Terminalen startas enklast på skolans datorer genom att högerklicka på skrivbordet och välja Open Terminal. Ett kommando skrivs in och exekveras genom att trycka på Enter. Dessutom har vissa kommandon argument och alternativ vilka är separerade med ett mellanslag. Alternativen är prefixade med - eller - -. För att få information om vilka argument eller alternativ som kan ges till ett visst kommando används help <kommando>. Till exempel om man vill veta hur cd fungerar, $ help cd cd: cd [-L [-P [-e]] [-@]] [dir] Change the shell working directory. [...] 1 http://www.cse.chalmers.se/~holmer/kurser/dat0550105/forelasn/filerf11.zip 2 http://git-scm.com/documentation/ 1
Här följer en lista och beskrivning av de vanligaste kommandon som behövs för denna övning, cd pwd ls Flyttar terminalens arbetskatalog. Sökvägen kan både vara relativ nuvarande katalog eller absolut genom att börja sökvägen med / eller. Aliaset pekar alltid på din hemkatalog medans / är filsystemets root-katalog. För att förflytta sig ett steg längre upp i filstrukturen används.. Skriver ut den nuvarande arbetskatalogen. Listar alla mappar/filer i arbetskatalogen. Användbara alternativ är -l som skriver ut en ny rad för varje mapp/fil. mkdir Skapar en mapp i den nuvarande arbetskatalogen. Genom att använda alternativet -p kan man skapa flera mappar samtidigt. Till exempel skulle mkdir a/b/c ge ett felmeddelande om inte a och b existerade medans a/b/c skulle skapa alla mappar om någon skulle saknas. touch Skapar en tom fil. Man kan skapa flera filer genom att ange flera argument. rm mv Tar bort en eller flera filer från filsystemet. För att ta bort en mapp måste man använda alternativet -r. Flyttar en mapp eller fil till en annan plats i filsystemet. 1.2 Installera Git Observera att detta inte behöver göras på Chalmers linuxdatorer då git redan är installerat och kräver endast konfigurering vilket beskrivs i Avsnitt 1.4. Windows datorerna på Chalmers har dock inte git installerat och det finns i dagsläget ingen möjlighet att installera det. För installation på egen dator så rekommenderas den terminalbaserade klienten med samma argument som i introduktionen. Dock så finns det vissa grafiska klienter som installerar både sin egen klient samt den terminalbaserade klienten. Följande rekommenderas för respektive operativsystem, Windows / OSX Här rekommenderas för oerfarna användare att installera Github Desktop 3. Detta kommer både installera en grafisk klient och ett så kallat Git Shell, vilket är den terminalbaserade klienten, vilket man kommer åt genom att klicka på kugghjulet i det översta högra hörnet. Alternativt kan man installera den officiella klienten 4 men detta kräver mer konfigurerarande för autheniseringen till en eventuell hostingsida. *nix Installera git via respektive distros pakethanterare (kan även vara installerat per automatik). Notera att detta kräver mer konfiguration av eventuella SSH-nycklar för användning av Github / Bitbucket. 3 http://desktop.github.com 4 http://www.git-scm.com/downloads 2
1.3 Hosting för repos I era projektgrupper vill ni troligtvis dela ett gemensamt repo som finns åtkomligt för alla medlemmar. Det finns ett antal tjänster som ger gratis tillgång till ett externt repo, github.com, ger tillgång till ett obegränsat antal med publika repos, som alla kan undersöka men inte uppdatera, utan kostnad. Genom att registera sig som student får man även deras billigaste paket utan kostnad under studietiden. Detta ger 5 privata repos som kräver tillstånd för att att hitta på deras hemsida. bitbucket.com, ger som Github tillgång till ett obegränsat antal publika repo men även privata för grupper med färre än 6 deltagare. De har även studenterbjudanden för fler funktioner. Användning av dessa kan kräva att man konfigurerar extra parametrar än de som nämns i Avsnitt 1.4, till exempel SSH-nycklar. Om man använder sig utav Github Desktop klienten görs detta enkelt i det grafiska interfacet. För andra klienter finns det hjälpsidor på både Github och Bitbucket för att komma igång. 1.4 Konfigurera Git Innan man börjar använda git behöver man ställa in sitt namn och e-postadress som git kommer använda som identifierare för varje ändring. Den används även av diverse hosting hemsidor för att knyta en commit till en användare. Därav så bör man ha samma e-postadress i sin git klient som man använde när man registrerade sig på en av hostingsidorna. Namnet som man ställer in kan vara ett användernamn/alias eller ens riktiga namn. Alla som har tillgång till repot kommer åt informationen som du ställer in. För att ta del av studentrabatterna på hostingsidorna krävs att man använder sig utav sin chalmers tilldelade e-postadress (cid@student.chalmers.se). De flesta tjänster tillåter dock att man har fler än en e-postadress registrerad om man inte vill ha sin chalmers adress som den förvalda i gitklienten. För att ställa in namn och e-postadress används följande git kommandon, $ git config --global user.name "<namn>" $ git config --global user.email "<e-post>" Vilket kan verifieras med git config - -list, $ git config --list user.name=john Doe user.email=johndoe@example.com Dessa inställningar gäller för alla repos som har skapats/klonats på denna dator. Dock så gäller det inte för äldre commits, så det är mycket enklare att ha allt inställt korrekt innan man börjar. 2 Övningar Innan man börjar med övningarna bör man läsa igenom introduktionen och ladda hem arkivfilen från kurshemsidan. För att packa upp arkivfilen används förslagsvis, $ mkdir git-exercise $ cd git-exercise $ tar xzf <filnamn> 3
Ett annat bra hjälpmedel under övningarna är git help som även fungerar för varje enskilt git kommando. Till exempel ger git help init en beskrivning av git init. 2.1 Skapa lokala repon och incheckning av ändringar För att skapa ett lokalt repo (eng. repository) används git init. Skapa ett förslagsvis genom att, $ mkdir hello-world $ cd hello-world $ git init Initialized empty Git repository in [...]/hello-world/.git/ Alternativt kan man direkt ange i vilken katalog som repot ska skapas i, $ git init hello-world Initialized empty Git repository in [...]/hello-world/.git/ $ cd hello-world Git skiljer sig ifrån andra versionshanteringssystem på ett antal punkter. Den mest unika skillnaden är att git inte lagrar skillnader mellan filer utan en snapshot av hela filstrukturen. Detta ger användaren mycket kontroll och valfrihet hur man väljer att struktera sitt arbete med versionshanteringssystemet. Git löser detta mer hjälp av tre separata platser för att lagra information om filstrukturen, Arbetskatalogen, eng. working tree, den vanliga katalogen där inte incheckade ändringar förvaras. Det är filer i denna katalog som man ändrar med valfri editor. Datalagret, eng. data store, där alla äldre förändingar lagras tillsammans med all annan information så som taggar, branches. Hittas i.git mappen i repots rootkatalog. Uppsamlingsplatsen/index, eng. staging area, plats där filer mellanlagras innan de flyttas in i den slutgiltiga förvaringen som en commit. Uppsamlingsplatsen tillåter att man grupperar ändringar i flera filer till samma commit. Detta implementeras som en index fil i git, alltså lista av referenser till filer i arbetskatalogen. För att göra en incheckning flyttas filer från arbetskatalogen till uppsamlingsplatsen/indexet. Till detta används följande kommandon, git add [<filnamn>...] Lägger till en eller flera filer till indexet. Filerna behöver inte vara helt nya för git utan kommandot används även för att markera att ändringarna i en tidigare tillagd fil ska checkas in. git rm [<filnamn>...] Förklarar att för git att en tidigare incheckad fil ska tas bort. Kommandot tar även bort filen från arbetskatalogen på samma sätt som ett vanligt rm kommando skulle göra. git mv <från> <till> Flyttar eller döper om en tidigare incheckad fil. Om man vill ångra något av ovanstånde kommandon, alltså att flytta tillbaka filen från indexet till arbetskatalogen används, $ git reset HEAD [<filnamn>...] Observera att detta inte fungerar för den första commiten som görs i ett repo eftersom det inte finns någon referens till HEAD. Istället måste man använda git rm - -cached [<filnamn>...], om man missar - -cached kommer filen och hela dess innehåll att permanent tas bort från filsystemet. 4
Att lägga till nya filer eller förändingar i tidigare tillagda filer görs i två steg, Lägga till filer i indexet med git add / rm / mv. Checka in filerna från indexet in i git med en commit, mha git commit. För att se det nuvarande tillståndet indexet och vilka filer som git har upptäckt ändringar i används git status, $ git status On branch master Initial commit nothing to commit (create/copy files and use "git add" to track) Testa att skapa ett par filer och använd git add. Undersök vad som har hänt med git status. Exempelvis, $ touch README.txt HelloWorld.java $ git add README.txt $ git status On branch master Initial commit Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: README.txt Untracked files: (use "git add <file>..." to include in what will be committed) HelloWorld.java Filen README.txt har flyttats ifrån untracked till to be commited. För att slutföra förändringen skapar vi en commit enligt, $ git commit -m "Added a readme file." [master (root-commit) f17a9af] Added a readme file. 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 README.txt Där -m "Added a readme file." representerar en beskrivande kommentar. Man kan även välja att skriva kommentaren i valfri texteditor genom att utelämna -m. Kontrollera ännu en gång git status samt använd dig av git log. git log listar alla gjorda commits. Undersök vilka alternativ som finns för git log genom git help log. Förslagsvis kanske man vill se vilka filer som påverkades i den senaste commiten, 5
$ git log -p -1 commit f17a9af0a9d207d10c3696a33bacf9a725298a7f Author: John Doe <johndoe@example.com> Date: Sun Feb 14 23:37:26 2016 +0100 Added a readme file. diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..e69de29 Innan du börjar med nästa övning kan du testa att göra flera commits i flera kataloger och pröva att ta bort och flytta filer. Börja på nästa övning när du känner dig bekväm med ovanstående. 2.2 Gemensamma repon I förra övningen använde vi oss utav git init för att skapa ett tomt lokalt repo. Om man istället vill skapa ett lokalt repo som utgår från ett redan existerande repo används git clone. Detta är användbart om man vill sammarbeta kring ett gemensamt repo då en användare kan skapa ett repo och resterande kan klona detta. Packa nu upp den medföljande arkivfilen som innehåller ett antal färdiga repos som går att klona. Dessa existerar lokalt endast för övningens skull och finns vanligtvis på en extern server som alla projektmedlemmar kommer åt. Förslag på tjänster som tillhandahåller en sådan server finns i Avsnitt 1.3. Flytta terminalen till den katalog där arkivfilen har packats upp och klona det första repot, $ git clone repos/collaborate.git Cloning into collaborate... done. $ cd collaborate Undersök innehållet i det nya repot med de kommandon som diskuterats i Övning 1, git status och git log. Det finns två mindre buggar i Euler.java filen. Korrigera buggarna med en commit per bugg enligt arbetsgången i Övning 1. Alla ändringar och commits existerar endast i din lokala klon av orginal repot. För att dela med sig av ändringarna till alla andra genom att ladda upp dem till original repot används kommandot git push. Exempelvis, $ git push origin master De två argumenten till git push beskriver två väsentliga koncept i git, origin, vilken remote som ändringarna ska publiseras till. git clone har automatiskt skapat en remote vid namn origin som pekar på den URL som man klonade. master, vilken branch som ska publiseras. Huvudbranchen som git alltid skapar vid heter master. Mer om branching i Övning 6. Det kan vara något tröttsamt att hela tiden lägga till origin master efter varje git push. Man kan då ställa in vilka argument som ska vara standard med, $ git push --set-upstream origin master Om man nu använder bara git push utan att ange någon remote eller branch så kommer git anta att du vill ha origin master. 6
För att uppdatera det klonade repot används följande två kommandon, $ git fetch origin master $ git merge FETCH_HEAD Där git fetch hämtar eventuella ändringar och git merge slår ihop de hämtade ändringarna med dina egna (Mer om git merge i Övning 3). Alternativt kan man använda sig utav det inbyggda kortkommandot git pulll vilket utför samma sak som ovanstånde, $ git pull origin master Pröva nu att först pusha dina ändringar till det externa repot med git push och kontrollera git status. I bakgrunden har en simulerad användare gjort ändringar, pröva att hämta hem dessa med git pull. Detta borde resultera i, $ git push origin master Counting objects: 6, done. Delta compression using up to 12 threads. Compressing objects: 100% (4/4), done. Writing objects: 100% (6/6), 563 bytes 0 bytes/s, done. Total 6 (delta 2), reused 0 (delta 0) To [...]/repos/collaborate.git c7f74ba..a218ebd master -> master $ git pull origin master remote: Counting objects: 3, done. remote: Compressing objects: 100% (3/3), done. remote: Total 3 (delta 0), reused 0 (delta 0) Unpacking objects: 100% (3/3), done. From [...]/repos/collaborate * branch master -> FETCH_HEAD a218ebd..fe5f84d master -> origin/master Updating a218ebd..fe5f84d Fast-forward EulerVerifier.java 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 EulerVerifier.java Undersök det uppdaterade repot med git log samt en texteditor. I Figur 1 ses en topologisk representation av hur det olika commitsen hänger ihop. Pilarna markerar vilken förälder som varje commit (gröna rutor) har. HEAD och master visar på vilken commit som refersenserna pekar. Identifiera vilka commits i loggen som motsvarar de olika bokstäverna. HEAD master A B C D Figur 1 7
2.3 Konflikter vid ihopslagning av ändringar I en tidigare övning fick vi bekanta oss med kommandot git pull vilket var en sammanslagning av två andra kommandon där git merge var ett av dem. git merge försöker slå ihop förändringar först automatiskt med diverse strategier. En av dessa strategier såg vi Övning 2 där git pull utförde en fast-forward. Om ändringarna rör samma filer och specifikt samma rader kan inte git automatiskt avgöra vilka av ändringarna som ska sparas utan lämnar ansvaret till utvecklaren. Klona nu repot repos/basic-merging.git och utför, Undersök klassen BubbleSort.java vilken implementerar en enkel sorteringsalgoritm. Gör flera ändringar i sorteringsmetoden. Modifiera variabelnamn, gör utskrifter mellan varv i looparna, etc. Det viktiga är att sort-metoden är förändrad så mycket som möjligt. Skapa en commit med ändringarna. Försöker man nu publisera ändringarna mha, git push får man följande felmeddelande, $ git push origin master Counting objects: 3, done. Delta compression using up to 12 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 368 bytes 0 bytes/s, done. Total 3 (delta 1), reused 0 (delta 0) remote: error: hook declined to update refs/heads/master To [...]/repos/basic-merging.git! [remote rejected] master -> master (hook declined) error: failed to push some refs to [...]/repos/basic-merging.git Enligt felmeddelandet så existerar det ändringar i det externa repot som man försöker pusha till. I detta fall har en annan användare lyckats publisera sina ändringar innan dig. En illustration av detta kan ses i Figur 2 där C är dina ändringar. Innan man kan publisera sina ändringar måste man hämta hem de senaste med git pull, $ git pull origin master From [...]/repos/basic-merging * branch master -> FETCH_HEAD 8ae05e6..8388ca0 master -> origin/master Auto-merging BubbleSort.java CONFLICT (content): Merge conflict in BubbleSort.java Automatic merge failed; fix conflicts and then commit the result. origin/master A B HEAD C Figur 2 master Till skillnad från git pull i Övning 2 har git inte lyckats att automatiskt merga ihop ändringarna 8
i det lokala repot och det externa repot. Som git antyder finns det fel i BubbleSort.java, undersök filen. Git har markerat där filen skiljer sig åt mellan de båda ändringarna. Dina ändringar är markerade som HEAD och allt under likhetsteckena är de som hämtades hem från det externa repot. Välj en av lösningarna eller kombinera båda genom att ta bort det som git har lagt till och lämna kvar en färdig lösning. Därefter är det bara att bekräfta ändringen med, $ git add BubbleSort.java $ git commit --no-edit $ git push origin master Med alternativet - -no-edit låter du git själv skapa ett merge meddelande till den commit som har skapats. Undersök commiten med git log. origin/master A B D master C HEAD Figur 3 Den commit som nu har skapats kan nu framgångsrikt pushas till det externa repot eftersom commiten pekar på både C och B ifrån Figur 2. Hur allt hänger ihop efter sammanslagningen illustreras i Figur 3. Men om någon annan har hunnit merga sina ändringar mellan din hämtning och uppdatering tvingas du att ännu en gång merga ihop dessa ändringar med dina. För att underlätta detta är det viktigt att man håller sina ändringar och commits så små och självständiga som möjligt vilket minskar risken att en krock händer. Eftersom alla ändringar i denna övning är simulerade för att förenkla, vilket gör det svårt att själv öva på merging med detta repo. Mer övning kan fås enklast genom att lära sig om ett annat koncept i git, branching. Vilket beskrivs i Övning 6. 2.4 Korrigera en gjord commit Antag följande senario, $ git init amending $ cd amending $ touch A.java B.java $ git add A.java $ git commit -m "Aded A.java and B.java." Man har skapat två filer, A.java och B.java, men har gjort två små fel vid inläggningen till git. Detta undkom oss dock och man skapade commiten ändå. Felen vi gjorde, 1. Vi glömde lägga till B.java med git add. 2. En mindre felstavning i commit meddelandet. Detta löses enklast genom git commit - -amend vilket återskapar den senast gjorda commiten. Exempelvis, 9
$ git add B.java $ git commit --amend -m "Added A.java and B.java" Låt oss anta att man bara gjorde ett mindre fel och inte vill ändra på sitt commit meddelande. Då man kan istället för -m "..." använda sig utav no-edit vilket låter git återanvända samma commit meddelande. $ git commit --amend --no-edit Observera! Man bör endast använda git commit - -amend om man INTE har gjort denna commit publik, med till exempel git push. Detta eftersom den äldre commiten försvinner helt ifrån historien och ersätts. Om någon annan har denna commit kommer det uppstå problem eftersom ditt repo inte vet att den finns längre. 2.5 Stuva undan ändringar Det är praxis inom git att en commit bör vara så liten som möjligt och endast beröra en sak (En av anledningarna såg vi i Övning 3). Till exempel om man utvecklar ny funktionalitet och stöter på en äldre bugg bör man dela upp ändringarna i minst två separata commits. För att testa på detta utför följande, Skapa en enkel klass med en medveten bugg och en commit. Påbörja en ny buggfri metod i klassen med ny funktionalitet. Under utvecklingen av den buggfria metoden upptäcker du buggen och vill genast åtgärda dettta. Enligt resonemanget ovan bör detta läggas i en separat commit, men du har redan påbörjat en ny ändring. Genom att använda kommandot git stash kan man spara undan ändringar i som inte har commitats och återställa repot till den version som senast var commitad. Utför nu, Använd git stash för att spara undan början på den nya buggfria metoden. Korrigera buggen och skapa en commit. Använd git stash pop för att hämta undan de undansparade ändringarna. Slutför den buggfria metoden och skapa en commit. Undersök vad som händer om den medvetna buggen skulle finnas i den metod som du försökte lägga till ny funktionalitet i. Vad händer med git stash pop? Om tid finns bör du även undersöka andra varianter av git stash. Ta reda på vad du kan göra med git help stash. 2.6 Branching En branch i git är en enkel pekare till en viss commit. Genom att använda branches är det enkelt att testa ny funktionalitet samtidigt som man kan hoppa tillbaka till produktionskod och utföra eventuella snabbfixar. Det är även möjligt att publisera en branch till ett externt repo så att andra projektmedlemma kan se över ny kod utan att behöva dela med sig av ett helt nytt repo. Det finns hela utvecklingsflöden som är baserade på git och branches. Ett av de mest populära är Git Flow 5 där man förespråkar två separata huvudbranches en för kod som är redo för produktion och en annan som innehåller nyare oprövad kod. 5 http://nvie.com/posts/a-successful-git-branching-model 10
Man skapar en branch med git branch och shiftar mellan olika branches med git checkout. Exempelvis, $ git branch testing $ git checkout testing Alternativt med kortkommandot vilket både skapar en branch och byter till den, $ git checkout -b testing Läs igenom git help branch för att lära dig hur man listar alla branches samt hur man tar bort en branch. Därefter skapa ett par commits och verifiera med git log att de existerar (Figur 4 illusterar hur strukturen kan se ut). Byt därefter tillbaka till huvudbranchen med, $ git checkout master HEAD A B C master D E testing Figur 4 Nu har alla ändringar som gjordes i den nya branchen försvunnit (Verifiera med git log). Skapa ett par ändringar i huvudbranchen. En vanlig operation är att flytta in ändringar från en branch till en annan. Detta görs med git merge. Exempelvis om vi står i huvudbranchen, $ git merge testing Figur 5 visar hur trädstrukturen ser ut efter ihopslagningen med git merge. HEAD A B C F master D E Figur 5 testing Beroende på vilka ändringar du gjorde kan du få konflikter vid ihopslagningen precis som i Övning 3. Det kan därför vara smart att här testa olika ändringar och se vad som händer när man försöker slå ihop dem. 2.7 Git tillsammans med en IDE Ett repo bör i allmänhet endast innehålla källkod och eventuellt de gemensamma platformsoberoende resurser som krävs för att bygga projektet. Exempel på resurser kan vara bilder, ljudfiler, etc. Resterande filer som kompilerad binärkod eller IDE-specifika filer bör inte läggas till i repot. Detta eftersom filerna 11
skiljer sig åt mellan användare och varje enskild användare är tvingad till att återskapa filerna för sin platform vid varje uppdatering mot det gemensamma repot. För att slippa se filerna i git status som untracked kan man lägga till en.gitignore fil till repot. En.gitignore fil består av sökvägar som git ska exkludera från sina kommandon. Varje rad i filen representerar en sökväg som kan vara ett filnamn/katalognamn eller innehålla ett wildcard för att matcha en viss filändelse. Ett exempel, # En kommentar. mapp1/mapp2 mapp3.project Hemligt.java # Wildcard som matchar alla filer som har filändelsen class. *.class Denna fil bör delas mellan alla i projektet, fördelaktigt genom att checka in den i git, och bör editeras av alla medlemmar så att alla oönskade filer tas bort oberoende av operativsystem eller val av IDE. Github har sammanställt 6 en lista med olika.gitignore filer för diverse språk/ide/operativsystem vilket kan vara en bra utgångpunkt. För att använda till exempel Eclipse tillsammans med git bör man först initera eller klona ett repo och därefter skapa ett projekt i den katalog som git har skapat. Detta upprepas för alla projektmedlemmar och man bör se till att man inte råkar lägga till en av de olika projektfiler som Eclipse skapar. Detta genom att till exempel se över sin.gitignore fil. 6 https://github.com/github/gitignore 12