Jeg er ny i Git's komplekse forgreningsmuligheder. Jeg arbejder altid på en enkelt filial og overfører ændringer og skubber derefter jævnligt til min eksterne oprindelse.
Et eller andet sted for nylig, jeg gjorde en nulstilling af nogle filer for at få dem ud af commit staging, og senere gjorde en rebase -i
for at slippe af med et par nylige lokale commits. Nu er jeg i en tilstand, jeg ikke helt forstår.
I mit arbejdsområde viser git log
præcis hvad jeg forventer - jeg er på rette spor med de commits jeg ikke ønskede væk, og nye commits der, osv.
Men jeg har lige skubbet til det eksterne repository, og det, der er der, er anderledes - et par af de commits, jeg havde dræbt i rebasen, blev skubbet, og de nye commits, der er committet lokalt, er der ikke.
Jeg tror, at "master/origin" er løsrevet fra HEAD, men jeg er ikke 100% klar over, hvad det betyder, hvordan man visualiserer det med kommandolinjeværktøjerne, og hvordan man retter det.
Lad os først afklare hvad HEAD er, og hvad det betyder, når det er løsrevet.
HEAD er det symbolske navn for den aktuelt udcheckede commit. Når HEAD ikke er løsrevet (den "normale"1 situation: du har en gren tjekket ud), peger HEAD faktisk på en branches "ref", og grenen peger på commit'et. HEAD er således "knyttet" til en gren. Når du laver et nyt commit, opdateres den gren, som HEAD peger på, til at pege på det nye commit. HEAD følger automatisk med, da den blot peger på grenen.
git symbolic-ref HEAD
giver refs/heads/master
Grenen med navnet "master" er tjekket ud.git rev-parse refs/heads/master
giver 17a0299808078923f2d62811326d130de991d1a95a
Dette commit er den aktuelle tip eller "head" i mastergrenen.git rev-parse HEAD
giver også 17a0299998078923f2d62811326d130de991d1a95a
Dette er hvad det betyder at være en "symbolsk ref". Den peger på et objekt gennem en anden reference.Vi har HEAD
→ refs/heads/master
→ 17a0299808078923f2d62811326d130de991d1a95a
Når HEAD er løsrevet, peger den direkte på et commit - i stedet for indirekte at pege på et gennem en branch. Du kan tænke på en løsrevet HEAD som værende på en unavngiven gren.
git symbolic-ref HEAD
fejler med fatal: ref HEAD is not a symbolic ref
git rev-parse HEAD
giver 17a0299808078923f2d62811326d130de991d1a95a
Da det ikke er en symbolsk ref, skal den pege direkte på selve commit'et.Vi har HEAD
→ 17a029999808078923f2d6281111326d130de991d1a95a
Det vigtige at huske med en løsrevet HEAD er, at hvis den commit, den peger på, ellers ikke er refereret (ingen anden ref kan nå den), så vil den blive "dinglende", når du checker en anden commit ud. Til sidst vil sådanne "dinglende" commits blive fjernet gennem garbage collection-processen (som standard opbevares de i mindst 2 uger og kan blive opbevaret længere ved at blive refereret af HEAD's reflog).
1 Det er helt fint at udføre "normalt" arbejde med en løsrevet HEAD, du skal bare holde styr på, hvad du laver, så du undgår at skulle fiske tabte historikken ud af reflog'en.
De mellemliggende trin i en interaktiv rebasering udføres med en detached HEAD (delvist for at undgå at forurene den aktive branches reflog). Hvis du afslutter den fulde rebasering, opdateres din oprindelige gren med det samlede resultat af rebaseringen og HEAD knyttes igen til den oprindelige gren. Mit gæt er, at du aldrig har afsluttet den fulde rebase-proces; dette vil efterlade dig med en løsrevet HEAD, der peger på den commit, der senest blev behandlet af rebase-operationen.
For at genoprette din situation bør du oprette en gren, der peger på den commit, som dit afmonterede HEAD peger på i øjeblikket:
git branch temp
git checkout temp
(disse to kommandoer kan forkortes som git checkout -b temp
)
Dette vil genforbinde din HEAD til den nye temp
-gren.
Dernæst bør du sammenligne den aktuelle commit (og dens historik) med den normale gren, som du forventede at arbejde på:
git log --graph --decorate --pretty=oneline --abbrev-commit master origin/master temp
git diff master temp
git diff origin/master temp
(Du vil sandsynligvis eksperimentere med logindstillingerne: tilføj -p
, udelad --pretty=...
for at se hele logmeddelelsen, osv.)
Hvis din nye temp
-gren ser god ud, kan du måske opdatere (f.eks.) master
til at pege på den:
git branch -f master temp
git checkout master
(disse to kommandoer kan forkortes som git checkout -B master temp
)
Du kan derefter slette den midlertidige gren:
git branch -d temp
Til sidst vil du sandsynligvis ønske at skubbe den genetablerede historik:
git push origin master
Det kan være nødvendigt at tilføje --force
i slutningen af denne kommando for at pushe, hvis den eksterne gren ikke kan "spoles fremad" til det nye commit (dvs. du har droppet eller omskrevet et eksisterende commit eller på anden måde omskrevet en del af historikken).
Hvis du var midt i en rebase-operation, bør du nok rydde op i det. Du kan kontrollere, om en rebase var i gang ved at kigge efter mappen .git/rebase-merge/
. Du kan manuelt rydde op i den igangværende rebase ved blot at slette denne mappe (f.eks. hvis du ikke længere kan huske formålet med og konteksten for den aktive rebase-operation). Normalt ville du bruge git rebase --abort
, men det gør nogle ekstra nulstillinger, som du sandsynligvis ønsker at undgå (det flytter HEAD tilbage til den oprindelige gren og nulstiller den tilbage til den oprindelige commit, hvilket vil fortryde noget af det arbejde, vi gjorde ovenfor).
Se her for en grundlæggende forklaring på løsrevet hoved:
http://git-scm.com/docs/git-checkout
Kommandolinje til at visualisere det:
git branch
eller
git branch -a
får du output som nedenfor:
* (no branch)
master
branch1
* (no branch)
viser, at du er i detached head.
Du kunne være kommet til denne tilstand ved at lave en git checkout somecommit
osv. og den ville have advaret dig med følgende:
You are in 'detached HEAD' state. Du kan kigge rundt, lave eksperimentelle ændringer og committe dem, og du kan kassere alle commits du laver i denne tilstand uden at påvirke nogen grene ved at udføre en anden checkout.
Hvis du ønsker at oprette en ny gren til beholde de commits, du opretter, kan du gøre det det (nu eller senere) ved at bruge -b med checkout-kommandoen igen. Eksempel:
git checkout -b new_branch_name
Nu skal du få dem over på master:
Lav en git reflog
eller bare git log
og noter dine commits. Nu git checkout master
og git merge
commitsne.
git merge HEAD@{1}
Rediger:
For at tilføje, brug git rebase -i
ikke kun til at slette / dræbe commits, som du ikke har brug for, men også til at redigere dem. Du skal blot nævne "edit" i commit-listen, og du vil kunne ændre dit commit og derefter udstede en git rebase --continue
for at gå videre. Dette ville have sikret, at du aldrig kom ind på en løsrevet HEAD.