Reflog: redd arbeid som ser tapt ut
Den interaktive leksjonen trenger JavaScript. Teksten under er hele leksjonen.
Etter leksjonen kan du
- forklare hva reflogen registrerer, og hvorfor commits som ser tapt ut, som regel fortsatt finnes forstå
- redde commits etter
git reset --hardog ettergit branch -Dved å lage en branch fra en linje i reflogen anvende - angre en rebase med
ORIG_HEADeller branchens egen reflog anvende - forklare hvorfor reflogen ikke er en backup forstå
Forstå: En borte peker er ikke en borte commit
Her er main med tre commits. Så kjører du git reset --hard HEAD~2 ved en feil. Du ville bare kaste en filendring, ikke to commits.
main hopper tilbake til A. B og C er nå stiplet: ingen branch når dem, og git log viser dem ikke lenger. Men de er ikke slettet. Commitene ligger fortsatt i repositoryet. Det var bare lappen som flyttet seg.
Hvordan finner du dem igjen? Git fører en dagbok som heter reflog. Hver gang HEAD eller en branch flytter seg, skriver Git en linje om hvor den gikk. HEAD@{0} er der HEAD er nå, og HEAD@{1} der den var ett steg før. Her er det C.
Før du gjør noe annet, gir du commiten et navn. git branch rescue HEAD@{1} lager en ny branch der. B og C kan nås igjen, og du kan se deg rundt i ro og fred. Etterpå kan du flytte main tilbake.
Å slette en branch er samme historie. git branch -D rescue fjerner bare lappen, og B og C blir stiplet igjen. Git skriver ut ID-en branchen hadde. Branchens egen reflog forsvinner, men reflogen til HEAD husker fortsatt commitene.
En rebase etterlater også commits som ingen peker på: originalene. Her droppet planen test-commiten ved en feil, så du vil ha branchen tilbake der den startet. Hvilket navn peker dit?
En rebase flytter HEAD mange ganger, så HEAD@{1} er bare det siste steget inne i rebasen. Bruk heller ORIG_HEAD, den gamle tuppen Git lagret før rebasen. ORIG_HEAD husker bare den siste reset, merge eller rebase, så etter det er branchens egen reflog, feature@{1}, veien tilbake.
Reflogen er et sikkerhetsnett, ikke en backup. Den finnes bare i din egen .git-mappe og blir aldri pushet. Linjer for commits som ingen branch når, går som standard ut 30 dager etter at de ble skrevet, og da kan Git slette commitene. Og den kjenner bare commits, aldri ucommittede endringer.
Se demo: Redning etter reset --hard
Her er uhellet. Vi resetter main to commits tilbake, med hard. Begge commitene forsvinner fra branchen, og filene deres forsvinner fra mappen.
$ git reset --hard HEAD~2
Loggen viser bare det første snapshotet. Det ser ut som arbeidet er borte. I grafen er de to commitene stiplet.
$ git log --oneline
Nå åpner vi dagboken. Se etter de to commitene som forsvant.
$ git reflog
Linjen rett under toppen, HEAD@{1}, er commiten Add budget. Vi gir den et navn med en gang, med en rescue-branch. De stiplede commitene blir hele igjen.
$ git branch rescue HEAD@{1}
main ligger rett bak rescue på samme linje, så en merge blir en fast-forward. Begge filene er tilbake.
$ git merge rescue
Alt er tilbake. Når du har sjekket at innholdet stemmer, kan du slette rescue.
$ git log --oneline
Oppgaver
- Finn ut hvor HEAD var før resetten.
- Lag en branch
rescuepå commiten HEAD pekte på rett før resetten. - Flytt
maintilbake til de reddede commitene.
Se demo: Angre en rebase som gikk galt
Vi vil rydde feature mens vi rebaser den oppå main. I planen sletter vi en linje ved en feil, den med testen. En linje som mangler, betyr drop, og Git advarer oss ikke.
$ git rebase -i main
Test-commiten er borte fra branchen. Vi vil ha feature tilbake nøyaktig slik den var før rebasen.
$ git log --oneline
La oss lese de fem siste linjene i reflogen. Legg merke til hvor mange linjer rebasen skrev.
$ git reflog -5
ORIG_HEAD peker på tuppen fra før rebasen. En hard reset flytter feature tilbake dit. Det er trygt her, fordi vi ikke har noe som ikke er committet.
$ git reset --hard ORIG_HEAD
Forgreningen er tilbake, med begge login-commitene. Nå kan vi rebase på nytt, og denne gangen beholde alle linjene i planen.
$ git log --oneline --graph --all
Oppgaver
- Se på de fem siste flyttene til HEAD.
- Sett
featuretilbake nøyaktig slik den var før rebasen.
Øv: Overfør: en kafé med tre uhell
Et kafé-repository der tre ting gikk galt tidligere i dag. Du står på main. Les reflogen før du gjør noe.
Oppgaver
- Noen kjørte en hard reset ett steg tilbake, to ganger på rad. Flytt
maintilbake til commitenAdd price list. - Tidligere i dag ble branchen
experimentslettet medgit branch -D. Gjenopprett den, med commitenTry dark theme. - Tidligere i dag ble
menurebaset oppåmainmed feil område, og commitenAdd menuforsvant fra branchen. Settmenutilbake slik den var før rebasen.
Sjekk deg selv: Hent det fram igjen
git branch -D experiment skrev Deleted branch experiment (was f4edb90). Du angrer med en gang. Hva kjører du?
git reflog show experimentgit branch experiment f4edb90- Ingenting,
-Dslettet commitene
PC-en din dør. Kan en medstudent redde commitene du ikke har pushet, med git reflog i sin klone?
- Ja, reflogen deles via
origin - Nei, reflogen finnes bare i din egen
.git-mappe - Ja, hvis medstudenten kjører
git fetchførst
Du kjørte git reset --hard mens du hadde endringer som aldri var staget eller committet. Hva kan reflogen gi tilbake?
- Endringene, fra linjen før resetten
- Ingenting av dem, fordi Git aldri lagret dem
- Alt, hvis du er rask og handler innen 30 dager
Repetisjon fra «Brancher og HEAD»: du laget en commit med detached HEAD og byttet så til main. Hvordan tar du best vare på commiten?
- Ingenting, Git tar vare på alle commits for alltid
- Gi den en branch, for eksempel med
git branch keep <id> - Bytt tilbake til den og lag en ny commit oppå
Vanlige misforståelser
- «Etter
git reset --harder commitene slettet.» De finnes fortsatt. Ingen branch peker på dem, men reflogen gjør det. - «Reflogen er en backup.» Den er lokal, blir aldri pushet, går ut på dato og har aldri sett endringer du ikke committet.
- «
HEAD@{1}betyr alltid før min forrige kommando.» Det er HEADs forrige posisjon. En rebase flytter HEAD mange ganger.
Les mer
Hva reflogen registrerer
Hver gang en ref flytter seg, legger Git til en linje i loggen til den refen i .git/logs/: den gamle posisjonen, den nye og hvorfor. Det finnes én reflog for HEAD og én for hver lokale branch. git reflog viser HEADs, git reflog show main viser branchens, og git log -g viser de samme linjene i loggformat.
$ git reset --hard HEAD~2
HEAD is now at a5ea1be Initial snapshot
$ git reflog
a5ea1be HEAD@{0}: reset: moving to HEAD~2
f4edb90 HEAD@{1}: commit: Add budget
ae89efa HEAD@{2}: commit: Write project plan
a5ea1be HEAD@{3}: commit (initial): Initial snapshot
$ git reflog show main
a5ea1be main@{0}: reset: moving to HEAD~2
f4edb90 main@{1}: commit: Add budget
ae89efa main@{2}: commit: Write project plan
a5ea1be main@{3}: commit (initial): Initial snapshot
Navnene kan brukes overalt der Git forventer en commit:
| Navn | Betyr |
|---|---|
HEAD@{2} |
der HEAD var for to flytt siden |
main@{1} |
der main var før sitt siste flytt |
main@{yesterday}, HEAD@{2.hours.ago} |
der refen var på det tidspunktet |
@{-1} |
branchen du stod på før forrige bytte |
HEAD flytter seg mye oftere enn en branch: ved hvert bytte og ved hvert steg i en rebase. Branchens egen reflog registrerer bare endringer i den branchen, så feature@{1} er ofte den enkleste veien tilbake etter en rebase.
En rolig redningsrutine
- Stopp. Ikke prøv flere kommandoer for å «fikse» det.
- Les
git status,git log --oneline --graph --alloggit reflog. - Gi det du finner et navn før alt annet:
git branch rescue <id>. En branch holder commitene nåbare uansett hva som skjer etterpå. - Undersøk
rescue, og flytt så branchen din tilbake med en merge, en reset eller en cherry-pick. - Slett
rescueførst når du har sjekket resultatet.
Slettede brancher
git branch -D fjerner lappen og branchens egen reflog, men skriver ut den gamle ID-en, og reflogen til HEAD lister fortsatt hver commit HEAD har vært innom:
$ git branch -D rescue
Deleted branch rescue (was f4edb90).
$ git branch rescue f4edb90
ORIG_HEAD
Kommandoer som flytter HEAD kraftig, lagrer den gamle posisjonen i ORIG_HEAD først: git reset, git merge, git rebase og git am, og dermed også git pull. Vanlige git commit, git commit --amend, git switch, git cherry-pick og git revert rører den ikke. Det finnes bare én ORIG_HEAD, og neste slike kommando overskriver den, så bruk den med en gang. Merk at git reset --hard ORIG_HEAD selv er en reset, så etterpå peker ORIG_HEAD på der du nettopp var.
Når reflogen ikke har den: git fsck
Noen commits havner aldri i en reflog. En droppet stash er det klassiske eksempelet: git stash drop fjerner reflog-linjen til refs/stash, men stash-commiten blir liggende i objektdatabasen. git fsck lister slike dangling objekter, som ingenting peker på:
$ git stash
Saved working directory and index state WIP on main: a5ea1be Initial snapshot
$ git stash drop
Dropped refs/stash@{0} (11acfee0f6acd5aeedfa14a2f0f123603c56be6f)
$ git fsck --lost-found
dangling commit 11acfee0f6acd5aeedfa14a2f0f123603c56be6f
$ git log --oneline -1 11acfee
11acfee WIP on main: a5ea1be Initial snapshot
$ git stash apply 11acfee
En dangling commit er tuppen av en tapt kjede: ingenting peker på den, heller ikke en annen commit. Vanlig git fsck regner reflogen som en referanse, men --lost-found ser bort fra den, akkurat som --no-reflogs. Derfor lister den også commits som bare reflogen husker. Se på hver av dem med git log -1 før du handler.
Filer du har staget, men aldri committet, ligger også i objektdatabasen, som blobs. Etter en uforsiktig git reset --hard kan du fortsatt finne dem, men uten filnavn:
$ echo "Draft menu: soup, bread" > menu.txt && git add menu.txt
$ git reset --hard
HEAD is now at a5ea1be Initial snapshot
$ git fsck --lost-found
dangling blob e064b3ad67676a5cbae5998a99c9b253d261c4b2
dangling commit 11acfee0f6acd5aeedfa14a2f0f123603c56be6f
$ git show e064b3ad
Draft menu: soup, bread
Endringer du aldri kjørte git add på, har Git aldri lagret. Ingen kommando kan gi dem tilbake.
Utløp og søppeltømming
Gamle reflog-linjer fjernes av git gc, som Git også kjører automatisk av og til. Standardverdiene:
| Innstilling | Standard | Gjelder |
|---|---|---|
gc.reflogExpire |
90 dager | reflog-linjer for commits som fortsatt er på branchen |
gc.reflogExpireUnreachable |
30 dager | linjer for commits branchen ikke lenger når |
gc.pruneExpire |
2 uker | løse objekter som ingenting viser til lenger |
Alderen regnes fra da linjen ble skrevet, ikke fra da commiten ble utilgjengelig. Du kan forlenge dem, for eksempel git config gc.reflogExpireUnreachable 90.days. Når en linje har gått ut og ingen ref når commiten, kan neste søppeltømming slette den for godt. git reflog expire --expire=now --all etterfulgt av git gc --prune=now sletter alt som ikke kan nås, med en gang. Det er siste steg når en hemmelighet allerede er skrevet ut av historikken, og nettopp derfor skal du aldri kjøre det i panikk: etterpå finnes ingen vei tilbake.
Hvorfor reflogen ikke er en backup
Reflogen finnes bare i din egen .git-mappe. Den blir ikke pushet, ikke hentet og ikke kopiert av git clone, og den går ut på dato. Den beskytter deg mot dine egne feil på din egen maskin. Mot en mistet PC trenger du en kopi på en annen maskin, for eksempel ved å pushe commitene dine til et remote.
Per-Arne's Git Good!