Hoe kan ik vrijstaand HEAD verzoenen met master/origin?

Ik ben nieuw in de branching complexiteit van Git. Ik werk altijd op een enkele branch en commit wijzigingen en push dan periodiek naar mijn oorsprong op afstand.

Ergens recentelijk, deed ik een reset van enkele bestanden om ze uit commit staging te halen, en later deed ik een rebase -i om van een paar recente lokale commits af te komen. Nu ben ik in een toestand die ik niet helemaal begrijp.

In mijn werkgebied laat git log precies zien wat ik'zou verwachten-- ik'ben op de goede weg met de commits die ik niet weg wilde hebben, en nieuwe commits daar, enz.

Maar ik heb net gepushed naar de remote repository, en wat's daar is is anders - een paar van de commits die ik'had gedood in de rebase werden gepushed, en de nieuwe commits die lokaal zijn gecommit'zijn er niet.

Ik denk dat "master/origin" is losgekoppeld van HEAD, maar ik'ben niet 100% duidelijk over wat dat betekent, hoe het te visualiseren met de opdrachtregel tools, en hoe het te repareren.

Oplossing

Laten we eerst verduidelijken wat HEAD is en wat het betekent als het wordt losgemaakt.

HEAD is de symbolische naam voor de huidige uitgecheckte commit. Als HEAD niet losgekoppeld is (de "normale"1 situatie: je hebt een branch uitgecheckt), dan wijst HEAD eigenlijk naar een branch's "ref" en de branch wijst naar de commit. HEAD is dus "verbonden" aan een branch. Als je een nieuwe vastlegging doet, dan wordt de tak waar HEAD naar wijst geüpdatet om naar de nieuwe vastlegging te wijzen. HEAD volgt automatisch, omdat het gewoon naar de tak wijst.

  • git symbolic-ref HEAD geeft refs/heads/master De branch genaamd "master" wordt uitgechecked.
  • git rev-parse refs/heads/master levert 17a02998078923f2d62811326d130de991d1a95a op Die commit is de huidige tip of "head" van de master branch.
  • git rev-parse HEAD geeft ook 17a02998078923f2d62811326d130de991d1a95a Dit is wat het betekent om een "symbolische ref" te zijn. Het wijst naar een object via een andere verwijzing.
    (Symbolische refs werden oorspronkelijk geïmplementeerd als symbolische links, maar later veranderd in gewone bestanden met extra interpretatie, zodat ze gebruikt konden worden op platformen die geen symlinks hebben).

We hebben HEADrefs/heads/master17a02998078923f2d62811326d130de991d1a95a

Als HEAD onthecht is, wijst het direct naar een commit-in plaats van indirect naar een commit via een branch. Je kunt aan een vrijgemaakte HEAD denken als zijnde op een naamloze branch.

  • git symbolic-ref HEAD faalt met fatal: ref HEAD is geen symbolic ref
  • git rev-parse HEAD geeft 17a02998078923f2d62811326d130de991d1a95a Omdat het geen symbolische ref is, moet het direct naar de commit zelf wijzen.

We hebben HEAD17a02998078923f2d62811326d130de991d1a95a

Het belangrijke ding om te onthouden met een vrijstaande HEAD is dat als de commit waar het naar wijst verder niet gerefereerd wordt (geen andere ref kan het bereiken), dan zal het "bungelen" als je een andere commit checkout. Uiteindelijk zullen zulke bungelende commits door het garbage collectie proces verwijderd worden (standaard worden ze tenminste 2 weken bewaard, en ze kunnen langer bewaard worden doordat ze door HEAD's reflog verwezen worden).

1 Het is prima om "normaal" werk te doen met een losgekoppelde HEAD, je moet alleen bijhouden wat je aan het doen bent om te voorkomen dat je gedropte geschiedenis uit de reflog moet vissen.


De tussenstappen van een interactieve rebase worden gedaan met een vrijstaande HEAD (gedeeltelijk om te voorkomen dat de reflog van de actieve branch vervuild raakt). Als je de volledige rebase operatie afrondt, zal het je originele branch updaten met het cumulatieve resultaat van de rebase operatie en HEAD weer aan de originele branch koppelen. Mijn gok is dat je het rebase proces nooit volledig voltooid hebt; dit zal je achterlaten met een los HEAD die wijst naar de commit die het meest recent door de rebase operatie verwerkt is.

Om van je situatie te herstellen, zou je een branch moeten maken die wijst naar de commit waar je ontkoppelde HEAD momenteel naar wijst:

git branch temp
git checkout temp

(deze twee commando's kunnen afgekort worden als git checkout -b temp)

Dit zal je HEAD opnieuw hechten aan de nieuwe temp branch.

Vervolgens moet je de huidige commit (en zijn historie) vergelijken met de normale branch waar je verwacht aan te werken:

git log --graph --decorate --pretty=oneline --abbrev-commit master origin/master temp
git diff master temp
git diff origin/master temp

(Je zult waarschijnlijk willen experimenteren met de log opties: voeg -p toe, laat --pretty=... weg om de hele log boodschap te zien, etc.)

Als je nieuwe temp branch er goed uitziet, wil je misschien (b.v.) master updaten om er naar te verwijzen:

git branch -f master temp
git checkout master

(deze twee commando's kunnen worden afgekort als git checkout -B master temp)

Je kunt dan de tijdelijke branch verwijderen:

git branch -d temp

Tenslotte zul je waarschijnlijk de herstelde historie willen pushen:

git push origin master

Je moet misschien --force aan het eind van dit commando toevoegen om te pushen als de remote branch niet "fast-forwarded" kan worden naar de nieuwe commit (i.e. je hebt een bestaande commit laten vallen, of herschreven, of op een andere manier een stukje geschiedenis herschreven).

Als je in het midden van een rebase operatie zat, dan moet je het waarschijnlijk opruimen. Je kunt controleren of een rebase bezig was door naar de map .git/rebase-merge/ te zoeken. Je kunt de lopende rebase handmatig opruimen door gewoon die directory te verwijderen (bijvoorbeeld als je het doel en de context van de actieve rebase operatie niet meer weet). Normaal gesproken zou je git rebase --abort gebruiken, maar dat doet wat extra resetten dat je waarschijnlijk wilt vermijden (het verplaatst HEAD terug naar de originele branch en zet het terug naar de originele commit, wat wat van het werk dat we hierboven gedaan hebben ongedaan zal maken).

Commentaren (22)

Kijk hier voor basisuitleg van los hoofd:

http://git-scm.com/docs/git-checkout

Opdrachtregel om het te visualiseren:

git branch

of

git branch -a

dan krijg je output zoals hieronder:

* (no branch)
master
branch1

De * (no branch) laat zien dat je in vrijstaand hoofd bent.

Je had in deze toestand kunnen komen door een git checkout somecommit etc. te doen en het zou je gewaarschuwd hebben met het volgende:

You are in 'detached HEAD' state. U kunt rondkijken, experimentele wijzigingen maken en deze committen, en je kunt commits die je in deze staat maakt negeren staat zonder enige branches te beïnvloeden door nog een checkout uit te voeren.

Als je een nieuwe branch wilt maken om commits die je maakt te behouden, dan kun je dat doen (nu of later) door -b te gebruiken met het checkout commando te gebruiken. Voorbeeld:

git checkout -b new_branch_name

Nu, om ze op master te krijgen:

Doe een git reflog of zelfs gewoon git log en noteer je commits. Nu git checkout master en git merge de commits.

git merge HEAD@{1}

Bewerken:

Om toe te voegen, gebruik git rebase -i niet alleen voor het verwijderen / doden van commits die je niet'nodig hebt, maar ook voor het bewerken ervan. Vermeld gewoon "edit" in de commit lijst en je zult in staat zijn om je commit te wijzigen en dan een git rebase --continue uit te geven om door te gaan. Dit zou ervoor gezorgd hebben dat je nooit in een losgekoppelde HEAD terecht zou zijn gekomen.

Commentaren (2)

Zet je losgemaakte commit op zijn eigen branch

Voer eenvoudig git checkout -b mynewbranch uit.

Voer dan git log uit, en je zult zien dat de commit nu HEAD is op deze nieuwe branch.

Commentaren (2)