Praktische GIT handleiding
Git zal je vast niet onbekend klinken, tenzij je onder een steen verborgen zit! Git is een razend populair (mede dankzij GitHub en de vele open-source projecten) versie beheer systeem dat net even iets anders werkt als zijn traditionele voorgangers zoals CVS of Subversion. Beide heb ik ook gebruikt (met name Subversion) en Git gebruiken was voor mij een behoorlijke omslag. Dit vooral om de manier waarop Git werkt. Je kunt zovaak lokaal committen als je wilt en 'pushed' je wijzingen naar een remote. In deze tutorial wil ik niet al teveel in gaan op wat Git is, hoe je het installeert of welke werkmodellen er zijn (distributed, etc) maar ga ik ervan uit dat je overtuigd bent in de kracht van Git en je al enigzins verdiept hebt in waar het vandaan komt etc. Ook voor het installeren zijn goede tutorials te vinden en op een standaard Linux machine stelt het ook niet veel voor. Omdat ik nu een kleine periode met Git werk wil ik gewoon graag mijn kennis tot heden delen met jullie en zullen er wellicht dingen zijn die niet kloppen of ik zelf ook nog niet goed begrijp. Wanneer je zoiets opmerkt corrigeer me gerust! Wil je ergens meer input over of meer informatie laat het dan ook weten zodat ik kan kijken ik of de tutorial kan aanvullen of verbeteren! Ik wil in de toekomst nog wat meer over het mergen van branches toevoegen aan mijn tutorial gezien daar heel veel mogelijkheden voor zijn. Daarnaast zal ik ook de commando's git fetch en git submodule willen toevoegen als de tijd het toe laat. Tot nu moeten jullie het hier even mee doen :) Ik hoop dat met deze tutorial, git twijfelaars overtuigd raken en zich vooral kunnen bezig houden met schrijven van goede code dan heel de tijd in de knoop te zitten met Git :)
Git clone of git init
Git clone
Je maakt een clone van de remote repository. Let op dat bij het clonen letterlijk alles wordt opgehaald, namelijk; tags en branches wat best veel kan zijn en dus niet altijd handig is als je op een mobiele data verbinding zit. Een voorbeeld:
git clone ssh://server/path/project.gitZal een map "project" aanmaken in de map waar je nu in zit en de master branch uitchecken. Je kunt vanaf dan werken met Git.
Er zijn meerdere protocollen om remote met Git te werken. De bovenstaande is SSH welke ik zelf fijn vindt omdat deze ook secure is. Wanneer je SSH als protocol gebruikt houdt er dan wel rekening mee dat voor iedereen die toegang moet krijgen tot de remote git repository een SSH account op de desbetreffende server moet hebben. Je kunt ook https gebruiken en je zult dan de git-daemon met behulp van apache configuratie moeten aanroepen. Je kunt dan bijvoorbeeld werken met een .htpasswd file.
Er zijn nog een aantal opties die je mee kan geven aan git clone zoals --quiet (spreekt voor zich lijkt me), --origin, --mirror, --shared, etc maar ik heb ze tot heden nog niet gebruikt en kan er daarom ook niet veel over vertellen. Git help to the rescue!
Git init en git remote
Een andere manier is, dat je bijvoorbeeld in de trein zit met je laptop en een geweldig idee hebt en wilt gaan ontwikkelen en je niet beschikt over een internet verbinding. Je kunt dan direct beginnen door het volgende te doen:
git init mijnprojectDit zal een map "mijn project" aanmaken vanuit de huidige map waar je in zit en een lege repository aanmaken. Git maakt direct een master branch aan en checked deze uit. Vooral bij nieuwe projecten is dit handig, want je kunt dan alle bestanden die je wilt "importeren" plakkken in de "mijnproject" map en telkens git commit doen.
Wanneer je thuis bent en wel over internet beschikt "hang" je nieuwe repository aan een remote:
git remote add origin ssh://server/path/mijnproject.gitLet er wel op dat deze bare repository wel aangemaakt moet zijn op de betreffende server.
Wanneer je de remote aan je lokale repository hebt gekoppeld en je commits wilt "pushen" doe je de eerste keer:
[code]git push origin master[/b]
Zodat remote de branch wordt aangemaakt. In het vervolg kun je gewoon "git push" doen.
of git init lekker committen etc en een remote toevoegen
Git pull
Faster-forward
Git pull probeert standaard een faster-forward uit te voeren op je lokale branch. Dit beteken dat Git kijkt of je eigen branch niet voorloopt op de remote en als dat niet het geval is dan kunnen de remote commits op je HEAD gezet worden zonder conflicten en is de pull klaar.
Pull met merge
Wanneer er geen faster forward gedaan kan worden, dan zal Git automatisch een merge proberen te doen. Meer hierover kun je lezen op de pagina "git merge en git rebase". Stel dat je een automatische merge wilt voorkomen geef je de optie --ff-only mee. Dan zal git de remote commits alleen ophalen als er een faster forward gedaan kan worden. Wanner dat niet het geval is wordt de pull gestopt. Wil je dat er wel een automatische merge gedaan wordt maar niet automatisch gecommit wordt NA de merge geef dan --no-commit mee. Je zult dan, indien er geen faster-forward is gedaan zelf nog moeten committen, je kunt dan eventueel nog wijzingen aanbrengen dus.
Git pull repository:branch
Wanneer je een "git pull" doet zonder specificatie (wordt ook refspec genoemd) dan zal Git proberen de remote commits op te halen in de branch waar je op zit. Wanneer je lokale branch naam anders is als remote moet je dit wel goed configureren anders zal git je vertellen dat hij niet weet waar hij de content vandaan moet halen. Maar dit zal niet vaak voorkomen. Je kunt in zo'n geval ook zelf aangeven waar Git de content uit moet halen:
git pull origin masterHier kleeft alleen een gevaar aan, dat is dat wanneer je doet vanuit de verkeerde branch ("anderebranch" bijvoorbeeld) dat Git zal proberen deze branches te mergen. Als je hier op tijd achterkomt is dat niet zo'n probleem dan doe je:
git reset --hard HEAD^1Dat wilt zeggen, draai 1 commit terug. Die ene commit is dus je merge commit. Als je nog in merge status bent gebruik je logischerwijs HEAD ipv HEAD^1. Wanneer je merge al gepushed hebt wordt het lastiger, dan is "git revert" je redding.
Verder over Git pull..
Feitelijk (dat is ook wat de help zegt) is Git pull niets anders als een git fetch met een git merge erachter aan, en dat alle opties die git pull kent git fetch of git merge ook kent.
Git merge en git rebase
Mergen of rebasen via 'git pull'
Je gebruikt mergen wellicht al een tijde, Git doet dit namelijk automatisch voor je als in je branch aan het comitten bent geweest en ondertussen zijn er remote ook commits gepushed door andere ontwikkelaars. In zo'n geval kan er geen 'faster-forward' gedaan worden. Een faster forward kan alleen als je eigen branch niet veranderd is sinds de laatste pull of fetch. Standaard zal git dus een merge commit maken die de laatste commits remote en je lokale commits in een commit zal gaan samenvoegen.
Merge
Naast dat 'git pull' een merge doet kun je ook zelf mergen. Het resolven van conflicts werkt in beide gevallen hetzellfde. 'Git merge' gebruik je om 2 branches te mergen. Een voorbeeld:
gitt merge anderebranchHet is wel belangrijk dat 'anderebranch' wel lokaal beschikbaar is. Git zal dan alle commits die op 'anderebranch' zijn gedaan sinds dat de branch is gemaakt samenvoegen met de branch die je uitgechecked hebt in 1 nieuwe commit. Wanneer er geen conflicten zijn zal Git deze merge direct committen.
Als er wel conflicten zijn zal Git dit melden dat de auto merge voor bepaalde files niet is gelukt. Je kunt dan met je favoriete editor deze conflicten oplossen en als je dat gedaan hebt stage je deze files met 'git add conflictfile'. Wanneer je alle conflicten hebt opgelost dan voer je het commando 'git commit' uit. Je hebt dan in een editor de mogelijkheid om toelichting te geven over het conflict en zal het je snel duidelijk worden dat het nu gaat om een merge commit.
Na het comitten is je merge compleet en kun je deze pushen naar de remote. Wanneer je een conflict hebt na een git pull los je deze op precies dezelfde manier op.
Stel dat je halverwege het mergen bent, en je het niet meer ziet zitten kun je git reset gebruiken als volgt:
git reset --hard HEADJe gaat dan terug naar de laatste recente versie en je merge is dan terug gedraaid. De wijzigingen die je hebt gemaakt om het conflict op te lossen zijn dan ook ongedaan gemaakt. Je kunt eventueel --soft gebruiken als je dat niet wilt.
Git rebase
Een andere prachtige feature van git is 'git rebase'. De naam klinkt voor sommige misschien vaag maar na de volgende uitleg vindt je het hopelijk simpel.
Stel je wilt alle wijzigingen van 'anderebranch' in de master zetten. Dan kun je voor een merge kiezen, maar soms is rebasen eenvoudiger. Dit is vooral wanneer je de commits die op 'anderebranch' zijn gedaan overzichtelijk terug wilt kunnen zien na de rebase. Het is dan ook eenvoudiger om een individuele commit uit 'anderebranch' in de master later terug te draaien met "git revert". Ook blijft je tijdlijn mooier doordat je niet weer een aftakking maakt van je master branch en deze weer samenvoegt. Als je het programma "gitk" geïnstalleerd hebt zal het je snel duidelijk worden wat ik daarmee bedoel.
Meestal doe ik een rebase als ik weet dat 'anderebranch' maar 5 commits heeft bijvoorbeeld. Om een hele merge te maken vindt ik dan overbodig. Wanneer je een rebase doet zal Git proberen alle commits die in 'anderebranch' zijn gedaan in de juiste tijdsvolgorde 'opnieuw af te spelen'. Het verschil met een merge is dat die bovenop je HEAD wordt gezet. Een andere branch rebasen gaat als volgt:
git rebase anderebranchAls je dan in de master uitgechecked bent zal Git proberen alle commits in 'anderebranch' in de juiste tijdsvolgorde in te voegen in de master. Nog een voordeel t.o.v. van een merge is dat je in je Git log geen meldingen ziet die lijken op: "Merge branch 'anderebranch' of ssh://host/var... master" die weinig zeggen. Je kunt deze melding bij een merge wel overschrijven/wijzigen door:
git merge anderebranch -m "Cool new feature"Te doen. Als de merge in een keer lukt zal Git "Cool new feature" als message meegeven en geen vage "Merge branch.. etc".
Wanneer je een conflict hebt tijdens het rebase los je deze conflict per conflict op. Je ziet in je console dat Git het mislukt is om het conflict automatisch op te lossen, en zal de rebase tijdelijk stoppen. Je opent het bestand in je favoriete editor en lost het probleem op. Vervolgens doe je dan:
git add ./bestand/met/conflict.php
git rebase --continue
En dan zal Git weer doorgaan met rebasen. Je kunt ook --skip gebruiken als je vindt dat het conflict niet erg is (bijvoorbeeld bij een add of delete conflict). Wanneer je het niet meer vertrouwt doe je het volgende:
git rebase --abortEn alles wordt dan weer terug gedraait en HEAD is dan weer zoals voor de rebase.
Rebase via pull
Wanneer je "git pull" doet zal Git automatisch een merge doen als er geen faster-forward gedaan kan worden. Feitelijk worden jouw commits die niet remote staan, en remote commits die niet bij jou lokaal staan samengevoegd in een merge commit. Ik vindt dit vaak niet wenselijk omdat anderen dan niet goed kunnen zien wat je nou precies aangepast hebt. Bij een rebase zal git proberen jouw commits proberen in te voegen in de juiste tijdsvolgorde tussen de andere remote commits. Zo is overzichtelijk wie wat gedaan heeft.
Je doet dit als volgt:
[code]git pull --rebase[/b]
En het vervolg process zal precies hetzelfde zijn als hierboven. Wanneer een faster-forward mogelijk is zal Git de rebase negeren (want je loopt tenslotte niet voor op de remote branch). Het oplossen van conflicten gaat precies hetzelfde!
Met merge en rebase kan nog veel meer. Bijvoorbeeld interactief rebasen. En bij "git merge" en "git rebase" kun je nog een tal van opties meegeven. Ik kan je zeker aanraden om eens de help goed door te lezen van beiden en met het e.e.a. eens te experimenteren.
Git cherry-pick
git cherry-pick ac815af53a8e085729ea834b0c376c8329a8c8daEen belangrijk verschil ten opzichte van een rebase (want daar lijkt het een beetje op) is dat Git bij een cherry-pick geen rekening houdt met de tijdslijn in tegenstelling tot een rebase. Git plakt de commit gewoon bovenaan erop na je HEAD.
Conflicten oplossen
Het kan zijn dat een cherry-pick conflicten oplevert. Je bent dan nog in cherry-picking 'state'.
Je lost dan zo'n conflict op met je favoriete editor en stage je de file met "git add file". Hierna doe je een commit: "git commit".
Git checkout
Met "git checkout" kun je dus schakelen naar een andere branch om daar verder in te werken. Wanneer je dit doet zal git alle bestanden inchecken die in die branch zitten.
Belangrijk is dat alle wijzingen die je nog niet gestaged hebt (met "git add") meegenomen worden als unstaged files naar de branch waarin je uitchecked. Wanneer ze al wel gestaged waren krijg je een melding dat je ze moet stashen of committen. Stashen betekend parkeren.
Een voorbeeld:
git checkout swiftmailerUitchecken naar een branch die nog niet lokaal staat?
Stel een collega ontwikkelaar heeft een nieuwe branch gemaakt en deze remote beschikbaar gemaakt. Wanneer hij of zij dat gedaan heeft betekend dat nog niet dat jij automatisch beschikking hebt over deze branch. Om aan deze branch verder te werken zul je eerst de branch lokaal moeten maken, uitchecken en een pull doen om alle remote content binnen te halen. Je zou dit als volgt kunnen doen:
git branch naambranch
git checkout naambranch
git pull origin naambranch
Maar met de -b optie is er ook een verkorte versie:
git checkout -b naambranch
git pull origin naambranch
Op die manier heb je dus de branch aangemaakt en meteen uitgechecked.
Git diff
Met git diff kun je verschillende dingen doen. Bijvoorbeeld de wijzigingen bekijken van een bestand die nog niet 'gestaged' of 'committed' is:
git diff ./path/somePHPFile.phpMet bovenstaand commando krijg je een overzicht van alle wijzingen in het bestand ten opzichte van de 'huidige' versie en daarmee bedoel ik de versie van dit bestand in de laatste commit lokaal of remote.
Commits of branches vergelijken
Nog handiger, is bekijken wat de verschillen zijn tussen de huidige versie en de vorige commit:
git diff HEAD^1..HEADHEAD^1 betekend, de huidige versie maar dan 1 commit terug in de branch waar je in zit. En ..HEAD zegt dat je het wil vergelijken met de meest recente commit. Het resultaat van een diff (als die er is), is vrij eenvoudig. Met de minnetjes zie je wat er weggegaan is en met de plusjes wat erbij gekomen is aan de code. Ondersteun je kleurtjes dan is dit ook respectievelijk rood en groen.
Om even terug te komen op het 'swiftmailer' voorbeeld, uit de de pagina "git branch", stel je hebt je swiftmailer feature af en voordat je deze branch wilt mergen of rebasen in de master je wilt kijken wat alle verschillen zijn en wat er allemaal omgebouwd wordt:
git diff swiftmailer..masterOp die manier krijg je dus alle verschillen tussen deze 2 branches, en weet je ook wat je eventueel aan conflicten kan tegenkomen als je ze gaat mergen.
Je kunt ook een commit-id gebruiken:
git diff HEAD..ac815af53a8e085729ea834b0c376c8329a8c8daJe kunt ook 2 commit-id's gebruiken, zoals je wellicht wel begrijpt.
Er zijn ook nog een tal van opties, die je met "git help diff" kan onderzoeken. Bijvoorbeeld de --shortstat optie:
git diff HEAD..master --shortstat
202 files changed, 2837 insertions(+), 13318 deletions(-)
Git branch
Branches gebruiken om nieuwe features te ontwikkelen
Een ander belangrijk voordeel van het gebruiken van branches is dat je bijvoorbeeld een test kan maken met een nieuwe versie van een framework. Stel je gebruikt nu PhpMailer in je hele applicatie om e-mail berichten te verzenden, en je wilt dit uitfaseren en ombouwen naar SwiftMailer. Dan besef je dat dit natuurlijk een behoorlijk groot process is en je niet de normale gang van zaken (op de master bv) in de weg wilt zetten. Je kunt dan een aparte branch maken (die je bijvoorbeeld 'swiftmailer' noemt) en daarin alles ombouwen naar de nieuwe library. Wanneer je klaar bent doe je vanuit de master een merge of rebase met deze nieuwe branch.
Lokaal of remote
Met branches werk je altijd lokaal. Er is in Git (zover ik weet) geen mogelijkheid om direct op de remote branch te werken. Wanneer je dus een branch aanmaakt staat deze alleen nog lokaal. Je moet de branch pushen om hem ook remote beschikbaar te maken en je code te kunnen delen met andere ontwikkelaars. Je branch pushen is niet altijd noodzakelijk wanneer je bijvoorbeeld alleen zelf aan de branch gaat ontwikkelen.
Branch aanmaken
Wanneer je een nieuwe branch aanmaakt in git doe je dat als volgt:
git branch swiftmailerNu heb je (alleen) lokaal de 'swiftmailer' branch aangemaakt op basis van de branch waar je nu in zit. Weet je niet in welke branch je zit? Met het commando:
git branch
[kees@host project]$ git branch
somecoolfeature
* master
swiftmailer
Zoals je hierboven kunt zien zit ik momenteel op de master branch. Wanneer je dus "git branch swiftmailer" uitvoert maak je dus een branch op basis van de master. Je gaat vanaf dan wel afwijken van de master en wijzigingen in de master branch zullen niet automatisch doorgevoerd worden in de swiftmailer branch. In de meeste gevallen is dit ook gewenst. Wanneer je wel wenst dat de swiftmailer branch de wijzigingen op de master bijhoudt dan geef je bij het aanmaken de optie --track me:
git branch --track swiftmailerWanneer je een git pull doet, zullen de wijzingen gemerged worden met de switftmailer branch. In meeste gevallen is het dan fijner om "git pull --rebase" te doen om de branch timeline overzichtelijk te houden.
Branch remote beschikbaar maken
Wanneer je dus een branch aangemaakt hebt staat deze alleen nog lokaal. Om je branch / code te delen met je collega's moet je deze 'pushen' om de branch remote beschikbaar te maken. Wanneer je de eerste keer je wijzingen in je lokale branch gaat pushen doe je het volgende:
git push origin swiftmailerWanneer je dit gedaan hebt zul je in de response zien dat Git een nieuwe branch heeft aangemaakt. Het meegeven van 'origin swiftmailer' hoeft alleen de eerste keer hierna kun je gewoon 'git push' doen als je commits naar de git repository wilt versturen.
Branch verwijderen
Het kan zijn dat je een bepaalde branch al gemerged hebt in de master of dat een bepaalde feature/experiment niet meer nodig is. Het heeft dan geen zit meer om de branch nog te bewaren. Een branch verwijderen doe je als volgt:
branch -d swiftmailerWanneer de branch ook remote stond en je hem ook remote wilt verwijderen:
git push origin :swiftmailerLet op de dubbele punt, deze is belangrijk bij het verwijderen.
Branches opvragen
Wanneer je wilt weten welke branches er remote allemaal zijn, welke je nu uitgechecked hebt of welke branches bij je lokaal staan voer je het volgende commando uit:
branch -aDit commando geeft je een volledige lijst van alle branches zowel lokaal als remote, en de branch met het sterretje ervoor is de branch waar je momenteel op zit. Een voorbeeld:
git branch -a
swiftmailer
* master
remotes/origin/swiftmailer
remotes/origin/master
Je ziet aan bovenstaand voorbeeld dat alle remote branches ook lokaal beschikbaar zijn.
Be aware!
Een fout die ik zelf weleens gemaakt heb is dat ik bijvoorbeeld in de swiftmailer branch uitgechecked was en het volgende commando uitvoerde:
git push origin masterHet gevolg was dat ik mijn branch ging mergen in de master terwijl deze nog niet klaar was. Omdat dit met 'push' meteen remote staat moet je met "git revert --mainline" gaan spelen om het terug te draaien. Wanneer je een merge conflict hebt, dan heb je geluk en kun je nog "git reset --soft ...." doen om ervoor te zorgen dat de merge terug gedraait wordt. Het is daarom een wijze les om alleen bij het aanmaken van de branch de eerste keer "origin swiftmailer" erachter te zetten in de swiftmailer branch en in het vervolg alleen "git push" te doen.
Tot slot..
Hopelijk heb je hiermee een goede indruk gegeven wat de basis mogelijkheden zijn in het werken met branches. Er is echter nog veel meer mogelijk en zijn er vaak ook nog meerdere opties die je mee kunt geven om bepaalde taken te verrichten. Op internet en in de help is hier veel over te vinden.
Git revert
Indien men dus een merge commit wilt terug draaien moet men de --mainline optie meegeven. Deze optie accepteerd de waarde 1 en 2. Dit heeft te maken met hoe de working tree eruit moet zien nadat de revert uitgevoerd is. Met 1 verzoek je Git de working tree zo te laten zoals hij voor de commit was en met 2 zoals het na de commit was. Bij ons kozen we in de meeste gevallen voor '1'.
Wanneer je een normale commit wilt terug draaien is dit vrij eenvoudig:
git revert 9c9bc66967ff6a03f40a4627db7231a9cbd9221cWaarbij de hash uiteraard maar een voorbeeld is. Nadat je het commando hebt uitgevoerd zal er gevraagd worden om een commit message op te geven over je revert. Hier kun je bijvoorbeeld uitleggen waarom het terug gedraait wordt. Wanneer je dit niet wenst geef je de optie --no-edit mee.
Er zijn nog meerdere opties, en met git help revert zou je ze eens kunnen doornemen.
Git reset
Git reset --soft [commit-id]
Stel je hebt twee commits gedaan en nog niet gepushed (belangrijk!) en je hebt toch besloten deze terug te draaien en alles opnieuw te committen:
git reset --soft HEAD^2Of je zoekt met git log de goede hash op waar je precies naar terug wilt gaan:
git reset --soft f8ead412cfbb25d4383d80c27ce30e5a45c7129fAls alles goed gaat zul je iets van van 'HEAD is now at..'. Je commits heb je weggegooit maar een belangrijk verschil is dat je wijzingen niet zijn verwijderd maar 'gestaged' zijn, dus deze kun je nu opnieuw committen.
git reset --hard HEAD^2
Deze versie lijkt veel op bovenstaande behalve het feit dat niet alleen de commits worden weggegooit maar ook alle wijzigingen die gedaan zijn aan de files, en deze wijzigingen zijn niet terug te draaien. Wees er voorzichtig mee.
In sommige gevallen was mijn branch een rommel geworden, met merges e.d. dat ik even terug wou naar het laatste moment zoals in gitweb staat. In zo'n geval doe ik een git reset --hard laatste-commit-id-op-gitweb.
N.b: commit-id is de unieke hash die bij een commit hoort. Deze kun je met gitweb of het commando git log bekijken.
Git tag
Wanneer gebruik ik tags?
Ik gebruik zelf altijd tags als ik een nieuwe versie van mijn applicatie uitbreng. Dan heb ik altijd een moment opname van de applicatie op dat moment wat ook handig is voor een eventuele rollback.
Tag aanmaken
Een tag aanmaken is kinderlijk eenvoudig:
git tag V1.10Hiermee maak je een tag van de branch waar je op dit moment in zit. Je tag is nog niet remote beschikbaar en dit doe je met het volgende commando:
git push --tagsTag verwijderen
Een tag verwijderen is ook eenvoudig:
git tag -d V1.10En om het remote door te voeren:
git push --tagsWelke tags zijn er?
Je kunt sowieso in Gitweb zien welke tags er zijn maar stel je zit in de trein en je hebt geen internet kun je altijd dit commando gebruiken:
git tag
Reacties
0