Meny for Per-Arne's Git Good!
Fag og prosjekt Per-Arne's Git Good!

Feiljakt: log -S, blame og bisect

Modul 5: Verktøykassen · 14 min

Den interaktive leksjonen trenger JavaScript. Teksten under er hele leksjonen.

Etter leksjonen kan du

  • finne commiten som innførte en feil med git bisect og en test, og avslutte søket med git bisect reset anvende
  • finne commiten som la til, fjernet eller endret en tekst med git log -S og git log -G, og lese diffen med -p analysere
  • lese git blame for å se hvilken commit som sist endret hver linje, og forklare hvorfor det ikke beviser hvem som laget feilen forstå

Forstå: Historikken som åsted

Når noe er feil, spør du ofte: hvor kom denne teksten fra, eller når sluttet dette å virke? Git har verktøy for begge. Alle leser historikken du allerede har.

Med to bindestreker og et filnavn viser git log bare commits som endret den filen. For config.txt er det bare to, D og F. Legger du til -p, får du diffen til hver av dem.

git blame viser, linje for linje, hvilken commit som sist endret linjen. I config.txt kommer begge linjene fra F. Det er et godt sted å starte, men den som sist rørte en linje, er ikke nødvendigvis den som laget feilen.

git log -S med en tekst finner commits der antallet forekomster av teksten endret seg, altså der den dukket opp, forsvant eller ble flyttet til en annen fil.

Noen ganger vet du ikke hvilken tekst som er feil, bare at det virket før. Da bruker du git bisect. Du oppgir en god og en dårlig commit, og Git sjekker ut en commit midt mellom dem.

For hver commit Git sjekker ut, svarer du god eller dårlig, og området som gjenstår halveres. Tusen krever bare omtrent ti.

Med git bisect run og en test gjør Git jobben selv. Avslutningskoden null betyr god, 125 betyr hopp over, og en annen kode fra 1 til 127 betyr dårlig.

Se demo: Bisect: når sluttet det å virke?

Brukerne melder at prisen er feil. Testen bekrefter det: 100 kroner skal bli 125 med moms, men blir 152. Ved taggen v1.0 virket det.

$ sh test.sh

Vi starter et binærsøk. Git trenger nå to opplysninger: en commit som er dårlig, og en som er god.

$ git bisect start

Commiten vi står på, er dårlig. Det har vi nettopp sett.

$ git bisect bad

Taggen v1.0 er god. Da har Git begge endene og hopper straks til en commit midt mellom. Feilen ligger i en av de sju commitene etter den gode enden. Hvor mange må vi teste?

$ git bisect good v1.0

I stedet for å teste for hånd lar vi Git kjøre testen. Git leser koden testen avslutter med, svarer god eller dårlig og fortsetter. Etter tre tester har vi svaret: Tidy config file er den første dårlige commiten.

$ git bisect run sh test.sh

Se på grafen: HEAD står på den siste commiten Git testet, ikke på den skyldige. Svaret står i utskriften. git bisect reset avslutter søket og tar oss tilbake til main.

$ git bisect reset

Oppgaver

  1. Fortell Git at taggen v1.0 er god.
  2. Finn den første dårlige commiten. La gjerne Git kjøre testen sh test.sh for deg.
  3. Avslutt søket og gå tilbake dit du startet.

Se demo: Tekstsporet: log, blame, -S og -G

Nå vet vi at feilen kom med Tidy config file. To bindestreker og filnavnet begrenser loggen til config.txt, og -p viser diffen til hver commit. Der er det: 25 ble fjernet og 52 lagt til, sammen med en ny kommentar. Det ser ut som en tastefeil under en opprydding.

$ git log -p -- config.txt

git blame viser hvilken commit som sist endret hver linje. Begge linjene kommer fra Tidy config file. Her er det riktig commit, men blame forteller bare hvem som sist rørte linjen.

$ git blame config.txt

Til slutt spør vi hvor den riktige verdien har vært. -S finner commits der en tekst dukker opp eller forsvinner. Hvilke commits får vi?

$ git log -S "vat=25" --oneline

-S "vat=" ville ikke funnet oppryddingen, for teksten vat= forekommer like mange ganger før og etter. -G søker i stedet etter endrede linjer som matcher et mønster. Derfor står Tidy config file øverst her.

$ git log -G "vat=" --oneline

Oppgaver

  1. Vis endringene i hver commit som rørte config.txt.
  2. Vis hvilken commit som sist endret hver linje i config.txt.
  3. Finn alle commits der teksten vat=25 dukket opp eller forsvant.

Øv: Overfør: en ny feil i et nytt prosjekt

Et annet prosjekt: et lite hilsen-skript med testen sh check.sh og en fil med innstillinger. Testen feiler på main, men virket ved taggen v0.1. Ingen demo denne gangen.

Oppgaver

  1. Finn den første commiten der sh check.sh feiler. Merk den med taggen first-bad, og avslutt søket slik at du står på main igjen.
  2. Noen har fjernet innstillingen Retry: 3 fra settings.txt. Finn commiten som fjernet den, og merk den med taggen removed-retry.
  3. Noen har endret verdien til Timeout i settings.txt, uten å si det i commitmeldingen. Finn commiten som endret verdien, og merk den med taggen timeout-change.

Sjekk deg selv: Hent det fram igjen

git blame viser at en commit fra Kari sist endret linjen der feilen synes. Hva vet du da?

  • At Kari laget feilen
  • At Karis commit var den siste som endret akkurat den linjen
  • At linjen har vært feil helt siden Kari kom inn i prosjektet

Hva må du ha før git bisect kan hjelpe deg?

  • En kjent god commit, en kjent dårlig commit og en test som gir samme svar hver gang
  • Navnet på filen der feilen er
  • Bare den dårlige commiten; Git finner den gode selv

Under git bisect run avslutter testen med kode 0. Hva betyr det for Git?

  • Commiten er dårlig, fordi testen fant noe
  • Commiten er god
  • Commiten skal hoppes over

Repetisjon fra «Brancher og HEAD»: Midt i et bisect-søk står HEAD løsrevet på en testcommit. Du lager en commit der med en ny test. Hva gjør du før git bisect reset, så du ikke mister den?

  • Ingenting. git bisect reset tar den med til main
  • Gir den et navn med git branch
  • Kjører git commit --amend, så den blir fast

Vanlige misforståelser

  • «git blame viser hvem som laget feilen.» Den viser hvilken commit som sist endret hver linje. Årsaken kan ligge et helt annet sted.
  • «Bisect finner feilen av seg selv.» Bisect trenger en god og en dårlig commit og en test som gir samme svar hver gang.
  • «Etter git bisect run står HEAD på den skyldige commiten.» HEAD står der Git testet sist. Svaret står i utskriften, og søket avsluttes med git bisect reset.

Les mer

Velg verktøy etter spørsmålet

Spørsmål Verktøy
Hvilke commits rørte denne filen? git log --oneline -- config.txt
Hva endret de? git log -p -- config.txt
Hvilken commit endret disse linjene sist? git blame -L 2,3 price.sh
Når dukket denne teksten opp eller forsvant? git log -S "vat=25"
Når endret en linje som matcher dette mønsteret seg? git log -G "vat=[0-9]+"
Når sluttet dette å virke? git bisect

Hvert svar er en kandidat, ikke en dom. Les diffen med git show, og bekreft årsaken med en målrettet test før du konkluderer.

Pickaxe: -S teller, -G søker

-S lister commits der antallet forekomster av en tekst endrer seg. -G lister commits der diffen har en lagt til eller fjernet linje som matcher et regulært uttrykk. Forskjellen betyr noe når en verdi endres på stedet. I leksjonens repository endret oppryddingen vat=25 til vat=52. Teksten vat= forekommer én gang før og én gang etter, så -S ser det ikke, mens -G gjør det:

$ git log -S 'vat=' --oneline
e3ef077 Move VAT rate to config file
e94df88 Add price calculator
$ git log -G 'vat=[0-9]+' --oneline
83bed6a Tidy config file
e3ef077 Move VAT rate to config file
e94df88 Add price calculator

Begge tar en sti etter -- for å snevre inn søket, og -p for å vise diffene. Legg til --follow i git log for én fil som har fått nytt navn. For å følge et linjeområde gjennom historikken bruker du git log -L 2,3:price.sh.

Blame uten å bli lurt

git blame viser, for hver linje, commiten som sist endret den. Sist endret er ikke det samme som forårsaket. Å rykke inn, flytte eller formatere en linje gjør den som formaterte til «forfatter». Valg hjelper:

  • -w overser endringer i mellomrom.
  • -M oppdager linjer flyttet innad i filen, -C linjer flyttet eller kopiert fra andre filer som ble endret i samme commit. Git trenger nok tegn for å kjenne igjen en flytting, så svært korte linjer blir ikke oppdaget.
  • -L 10,20 begrenser utskriften til noen linjer. -s skjuler forfatter og dato.
  • --ignore-rev eller en fil oppgitt med --ignore-revs-file hopper over kjente formateringscommits.
$ git blame sum.sh
^db3ccc3 (Ada 2026-09-01 10:00:00 +0200 1) total() {
7e088013 (Bob 2026-10-01 10:00:00 +0200 2)     echo $(($1 + $2))
^db3ccc3 (Ada 2026-09-01 10:00:00 +0200 3) }
$ git blame -w sum.sh
^db3ccc3 (Ada 2026-09-01 10:00:00 +0200 1) total() {
^db3ccc3 (Ada 2026-09-01 10:00:00 +0200 2)     echo $(($1 + $2))
^db3ccc3 (Ada 2026-09-01 10:00:00 +0200 3) }

Bob rykket bare inn linje 2. En ^ foran en ID markerer grensecommiten, her den første commiten. Blame svarer på «hvilken commit», aldri på «hvem sin skyld».

Bisect i dybden

Bisect trenger tre ting: en commit du vet er dårlig, en commit du vet er god, og en test som måler den samme egenskapen hver gang. git bisect start BAD GOOD setter begge endene på én gang. Git sjekker ut en commit omtrent i midten; i en historikk med merger er kandidaten ikke nødvendigvis nøyaktig midtpunktet. Du svarer git bisect good eller git bisect bad til Git skriver «is the first 'bad' commit». Bisect forutsetter at området går fra godt til dårlig én gang. Forsvinner feilen og kommer tilbake, finner den én av overgangene, ikke nødvendigvis den første.

Hopp over det du ikke kan teste. Bygger ikke en commit, eller kan testen ikke kjøres der, svarer du git bisect skip i stedet for å gjette. Git velger da en nabo. Er alle gjenværende kandidater hoppet over, lister Git dem alle og stopper, og du rapporterer usikkerheten i stedet for å late som du vet. Ett feil god/dårlig-svar gjør at bisect peker selvsikkert på feil commit.

Automatiser. git bisect run CMD kjører en kommando i hvert steg og leser avslutningskoden: 0 er god, 125 er hopp over, 1 til 127 unntatt 125 er dårlig, og alt annet avbryter søket. Et testskript kan derfor hoppe over commits der testen ikke gir mening:

test -f config.txt || exit 125
sh test.sh

Bruk egne ord. Leter du etter når noe endret seg, og ikke når det ble ødelagt, er ordene good og bad forvirrende. git bisect start --term-old=cheap --term-new=expensive lar deg svare git bisect cheap og git bisect expensive. git bisect terms minner deg på hvilke ord som gjelder.

Ta vare på sporet. git bisect log skriver ut hvert svar du har gitt. Lagre det i en fil, rett et feil svar i en editor, og spill det av igjen med git bisect replay:

$ git bisect log > bisect.log
$ git bisect reset
$ git bisect replay bisect.log

Avslutt alltid med reset. Under bisect står HEAD løsrevet (detached) på en testcommit. git bisect reset tar deg tilbake til branchen du startet fra. Skriv ned svaret først: etter en git bisect run står HEAD der Git testet sist, ikke nødvendigvis på den skyldige commiten.

Tester som lyver

Bisect er aldri bedre enn testen. En test som avhenger av nettverk, klokke eller hvor travel maskinen er, kan feile av grunner som ikke har noe med endringen å gjøre, og da peker bisect ut en uskyldig commit. Kjør testen to ganger på kandidaten og forelderen, les diffen, og skriv om mulig en regresjonstest som ville fanget feilen.

Offisiell dokumentasjon