# git-hook

## 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](/sv/portfolio/held-five-repositories-to-one-history-standard-165/):
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](/sv/portfolio/rehearsed-the-forge-side-ci-hook-151/)
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](/sv/portfolio/found-the-commit-hooks-and-the-gate-disagreeing-150/),
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:

```sh
#!/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 setup` gör här. En kommentar i webbplatsens `Taskfile.yml` beskriver den tysta varianten av
  felet: utan `pre-commit` på maskinen installerar `task setup` ingenting, varje hook uteblir utan
  ett ljud, och resultatet ser precis ut som ett repo som klarar sina kontroller.
- **`--no-verify` går rakt förbi dem.** `git commit --no-verify` kringgår hookarna `pre-commit` och
  `commit-msg`, `git push --no-verify` kringgår `pre-push`, och att sätta `core.hooksPath` till
  `/dev/null` stä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_DIR` så 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-push` som `(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](https://git-scm.com/docs/githooks) — 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](https://git-scm.com/book/en/v2/Customizing-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](https://git-scm.com/book/en/v2/Customizing-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](https://git-scm.com/docs/git-config) — inställningen `core.hooksPath`, för att flytta
  hookarna ut ur `.git/hooks` eller stänga av alla på en gång.
- [git commit](https://git-scm.com/docs/git-commit) — flaggan `--no-verify` och de två hookar den
  kringgår.
- [git push](https://git-scm.com/docs/git-push) — hooken `pre-push` och flaggan `--no-verify` som
  hoppar över den.

<https://engineer.company/sv/glossary/git-hook/>
