git-hook
ett skript som git kör när något händer
Vad det är
En git-hook är ett program som git själv startar vid en bestämd punkt i sitt eget arbete: innan en
commit registreras, när ett commitmeddelande har skrivits, innan en push lämnar maskinen eller när
en server tar emot en. Varje hook är en fil som heter efter sitt ögonblick, t.ex. pre-commit,
commit-msg eller pre-push, utan filändelse. Som standard letar git efter dem i .git/hooks inne
i repot, och inställningen core.hooksPath pekar git någon annanstans. Filen måste vara körbar,
eftersom git bortser från en fil utan körbarhetsbiten, men den kan skrivas i vilket språk som helst
som maskinen kör.
Hookar på klientsidan körs i en utvecklares egen klon och utlöses av operationer som commit och merge. Hookar på serversidan körs på maskinen som tar emot pushar, t.ex. en forge, och utlöses när pushade commits kommer fram.
Det som gör en hook till en vakt i stället för en notis är dess slutstatus. Hooken pre-commit tar
inga argument, och en slutstatus skild från noll avbryter commiten. Hooken commit-msg får
sökvägen till en fil med det föreslagna meddelandet och kan avvisa meddelandet eller redigera det.
Hooken pre-push får namnet på och adressen till det mottagande repot som argument och en rad per
referens på standard in, och en slutstatus skild från noll stoppar pushen innan något har skickats.
På servern körs pre-receive en gång per push och läser det gamla objektnamnet, det nya
objektnamnet och referensnamnet för varje referens, och avslutas den med en status skild från noll
uppdateras ingen av referenserna. Hookar som körs efter händelsen, t.ex. post-commit, är till för
notifiering och kan inte ändra utfallet.
När git init skapar ett repo fyller det hookkatalogen med exempelskript vars namn slutar på
.sample. Git kör ett sådant skript först när det har döpts om utan ändelsen.
Varför det spelar roll
En hook gör en kontroll som någon måste komma ihåg till en som sker av sig själv, i det billigaste ögonblicket. En formaterare eller en sökning efter hemligheter som körs när commiten görs fångar ett problem medan ändringen fortfarande är färsk i författarens huvud, snarare än vid en granskning flera dagar senare eller i en CI-körning efter att arbetet redan har delats.
Hookar är också sättet som en regel blir en egenskap hos repot snarare än hos den som råkar
committa. En konvention i en guide för bidragsgivare håller bara så länge folk läser guiden, medan
samma konvention i en commit-msg-hook gäller för varje commit.
De två grupperna skiljer sig åt i hur mycket de kan lova. Boken Pro Git påpekar att hookar på
klientsidan inte kopieras när ett repo klonas, och rekommenderar att policy upprätthålls på
serversidan. En hook på serversidan som pre-receive gäller för varje push från alla, oavsett vad
de har installerat lokalt. Kostnaden är tid: en hook körs vid varje commit eller varje push, och en
kontroll som gör det långsamt att committa lockar folk att hoppa över den.
Så ser det ut i praktiken
Den här webbplatsens eget repo installerar sina hookar via ramverket pre-commit, konfigurerat i
.pre-commit-config.yaml och installerat av task setup, som kör
pre-commit install --install-hooks. Filen installerar tre git-hookar. I steget pre-commit kör den
Prettier, markdownlint, stylelint, yamllint, codespell, Ruffs kontroller av lint och formatering, en
sökning efter hemligheter med gitleaks, en kontroll av emojier och en
kontroll av tonen. I steget commit-msg kör den ett enda skript som håller meddelandena till en
konventionell form utan emojier. I steget pre-push kör den hela kvalitetsgrinden, task check, som
en kommentar i filen uppskattar till ungefär sex minuter, ett pris värt att betala eftersom en push
är sällsynt. Filens inledning säger att hookarna aldrig ska kringgås med --no-verify, medan
kommentaren vid pre-push-hooken behåller --no-verify som nödutgång för det fall som verkligen
inte kan vänta.
En hook för commitmeddelanden håller också fyra repon till en och samma standard för historiken: ett konventionellt prefix och scope, en ämnesrad under en fast längd, inga emojier och inga tillskrivningsrader. Hooken avvisar allt som inte följer standarden i varje repo där arbete sker, så att standarden hör till repot snarare än till den som committar.
En hook på serversidan för företagets egen forge provkördes innan någon riktig push nådde den. En sådan hook körs i ett bare-repo utan arbetskatalog, i en miljö som forgen sätter upp, och läser de pushade referenserna från standard in, så att köra den för hand på en laptop bevisar lite. Provkörningen skapar ett bare-repo, installerar hooken, klonar, committar, pushar och verifierar både fallet där pushen godtas och fallet där den avvisas. Den hittade två fel som inte syntes i filen, och båda beskrivs nedan.
I ett projekt visade det sig att commit-hooken och kvalitetsgrinden körde olika kontroller, trots att ett dokument sa att hooken körde grinden. Ett test löser nu upp båda till de kontroller de faktiskt anropar och fallerar när mängderna skiljer sig åt. De fem som bara hade körts i grinden var de som läser CV-prosan, så koden skyddades vid varje commit och texten bara när någon kom ihåg det.
En hook på klientsidan behöver inget ramverk alls. Den här, sparad som .git/hooks/commit-msg och
gjord körbar, avvisar en commit vars ämnesrad inte börjar med en konventionell typ:
#!/bin/sh
# git passes the path of the proposed commit message as $1
pattern='^(feat|fix|docs|chore)(\([a-z0-9-]+\))?: .+'
if head -n 1 "$1" | grep -Eq "$pattern"; then
exit 0
fi
echo "commit-msg: start the subject with a type, as in 'fix(nav): ...'" >&2
exit 1
Var det går fel
- En ny klon kommer utan hookarna. Varje klon måste installera dem, och det är jobbet som
task setupgör här. En kommentar i webbplatsensTaskfile.ymlbeskriver den tysta varianten av felet: utanpre-commitpå maskinen installerartask setupingenting, varje hook uteblir utan ett ljud, och resultatet ser precis ut som ett repo som klarar sina kontroller. --no-verifygår rakt förbi dem.git commit --no-verifykringgår hookarnapre-commitochcommit-msg,git push --no-verifykringgårpre-push, och att sättacore.hooksPathtill/dev/nullstänger av alla hookar på en gång. En regel som måste hålla hör därför också hemma i en hook på serversidan.- Gits miljö följer med hooken in i andra repon. Git exporterar variabler som
GIT_DIRså att git-kommandon i en hook hittar rätt repo, och githooks(5) råder till att rensa dem när en hook arbetar i ett annat repo. Provkörningen mot forgen stötte på exakt detta: grinden checkade ut den pushade revisionen och körde testerna i den, testerna ärvde gits pekare till bare-repot, och nitton av dem blev röda på korrekt kod. - Standard in bär kantfallen. Provkörningens andra fel var en reservlösning som satte in en
platshållare som git inte kan slå upp när den saknade en referens att arbeta med. Eftersom värdet
kommer in på standard in visade sig felet som ett orelaterat fel mycket längre ned. Sidan noterar
också att forgen kommer att se tvingade uppdateringar och borttagna grenar, som provkörningen inte
konstruerar. Enligt githooks(5) når en borttagen referens
pre-pushsom(delete)med ett objektnamn av bara nollor. - En långsam hook hoppas över. Att föra in de fem prosakontrollerna i commit-hooken krävde att två av dem gjordes snabba nog för en tidsbudget före commit, och den sidan säger rakt ut att körtiden växer med innehållet och att det finns en punkt där en långsam hook kringgås.
- Formatet godkänns medan betydelsen lämnas okontrollerad. Som sidan om de fyra repona uttrycker det kontrolleras formatet men inte innehållet, så en ämnesrad som följer standarden men inte beskriver någonting passerar precis lika väl som en som förklarar ändringen.
Läs mer
- githooks documentation — varje hook som git stöder, när den körs, vad den får och vad dess slutstatus avgör.
- Pro Git: Git Hooks — hookar på klient- och serversidan, hur man installerar en och varför en klon lämnar dem kvar.
- Pro Git: An Example Git-Enforced Policy
— en
update-hook på serversidan som upprätthåller ett meddelandeformat och regler för åtkomst, med hookar på klientsidan som varnar före en push. - git config — inställningen
core.hooksPath, för att flytta hookarna ut ur.git/hookseller stänga av alla på en gång. - git commit — flaggan
--no-verifyoch de två hookar den kringgår. - git push — hooken
pre-pushoch flaggan--no-verifysom hoppar över den.