Come posso conciliare HEAD distaccato con master/origine?

Sono nuovo nella complessità della ramificazione di Git. Lavoro sempre su un singolo ramo e commetto le modifiche e poi periodicamente spingo alla mia origine remota.

Da qualche parte recentemente, ho fatto un reset di alcuni file per farli uscire dal commit staging, e successivamente ho fatto un rebase -i per sbarazzarmi di un paio di commit locali recenti. Ora mi trovo in uno stato che non capisco bene.

Nella mia area di lavoro, il git log mostra esattamente quello che mi aspetterei - sono sul treno giusto con i commit che non volevo che sparissero, e quelli nuovi lì, ecc.

Ma ho appena fatto un push al repository remoto, e quello che c'è lì è diverso - un paio di commit che avevo ucciso nel rebase sono stati spinti, e quelli nuovi commessi localmente non ci sono.

Penso che "master/origin" sia staccato da HEAD, ma non sono chiaro al 100% su cosa significhi, come visualizzarlo con gli strumenti a riga di comando, e come sistemarlo.

Soluzione

Prima di tutto, chiariamo cosa è HEAD e cosa significa quando è staccata.

HEAD è il nome simbolico del commit attualmente in check out. Quando HEAD non è staccata (la "normale"1 situazione: si ha un ramo in check out), HEAD punta effettivamente al "ref" di un ramo e il ramo punta al commit. HEAD è quindi "attaccata" ad un ramo. Quando fai un nuovo commit, il ramo a cui punta HEAD viene aggiornato per puntare al nuovo commit. HEAD segue automaticamente dato che punta solo al ramo.

  • git symbolic-ref HEAD produce refs/heads/master. Il ramo chiamato "master" viene controllato.
  • git rev-parse refs/heads/master produce 17a02998078923f2d62811326d130de991d1a95a Quel commit è l'attuale punta o "testa" del ramo master.
  • Anche git rev-parse HEAD produce 17a02998078923f2d62811326d130de991d1a95a. Questo è ciò che significa essere un "ref simbolico". Punta ad un oggetto attraverso qualche altro riferimento.
    (I ref simbolici erano originariamente implementati come link simbolici, ma in seguito furono cambiati in semplici file con un'interpretazione extra in modo che potessero essere usati su piattaforme che non hanno symlink).

Abbiamo HEADrefs/heads/master17a02998078923f2d62811326d130de991d1a95a

Quando HEAD è staccata, punta direttamente a un commit, invece di puntare indirettamente a uno attraverso un ramo. Puoi pensare ad una HEAD staccata come se fosse su un ramo senza nome.

  • git symbolic-ref HEAD fallisce con fatal: ref HEAD is not a symbolic ref.
  • git rev-parse HEAD produce 17a02998078923f2d62811326d130de991d1a95a. Poiché non è un ref simbolico, deve puntare direttamente al commit stesso.

Abbiamo HEAD17a02998078923f2d62811326d130de991d1a95a

La cosa importante da ricordare con un HEAD staccato è che se il commit a cui punta è altrimenti non referenziato (nessun altro ref può raggiungerlo), allora diventerà "dangling" quando farai il checkout di qualche altro commit. Alla fine, tali commit dangling saranno tagliati attraverso il processo di garbage collection (per impostazione predefinita, sono tenuti per almeno 2 settimane e possono essere tenuti più a lungo essendo referenziati dal reflog di HEAD).

11 Va benissimo fare un lavoro "normale" con una HEAD staccata, devi solo tenere traccia di quello che stai facendo per evitare di dover ripescare la cronologia abbandonata dal reflog.


I passi intermedi di un rebase interattivo sono fatti con una HEAD staccata (in parte per evitare di inquinare il reflog del ramo attivo). Se finisci l'operazione di rebase completa, aggiornerà il tuo ramo originale con il risultato cumulativo dell'operazione di rebase e riattaccherà HEAD al ramo originale. La mia ipotesi è che tu non abbia mai completato completamente il processo di rebase; questo ti lascerà con una HEAD staccata che punta al commit che è stato elaborato più recentemente dall'operazione di rebase.

Per recuperare la tua situazione, dovresti creare un ramo che punti al commit attualmente puntato dalla tua HEAD staccata:

git branch temp
git checkout temp

(questi due comandi possono essere abbreviati come git checkout -b temp);

Questo riattaccherà il tuo HEAD al nuovo ramo temp.

Successivamente, dovresti confrontare il commit corrente (e la sua storia) con il ramo normale su cui ti aspettavi di lavorare:

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

(Probabilmente si vorrà sperimentare con le opzioni di log: aggiungere -p, lasciare fuori --pretty=... per vedere l'intero messaggio di log, ecc.)

Se il tuo nuovo ramo temp sembra buono, potresti voler aggiornare (per esempio) master per puntare ad esso:

git branch -f master temp
git checkout master

(questi due comandi possono essere abbreviati come git checkout -B master temp);

Puoi quindi cancellare il ramo temporaneo:

git branch -d temp

Infine, probabilmente vorrai fare un push della storia ristabilita:

git push origin master

Potresti aver bisogno di aggiungere -force alla fine di questo comando per fare il push se il ramo remoto non può essere "accelerato" al nuovo commit (cioè hai abbandonato, o riscritto qualche commit esistente, o altrimenti riscritto qualche pezzo di storia).

Se eri nel mezzo di un'operazione di rebase probabilmente dovresti ripulirlo. Puoi controllare se un rebase era in corso cercando la directory .git/rebase-merge/. Puoi pulire manualmente il rebase in corso semplicemente cancellando quella directory (ad esempio se non ricordi più lo scopo e il contesto dell'operazione di rebase attiva). Di solito useresti git rebase --abort, ma questo fa un reset extra che probabilmente vorresti evitare (sposta HEAD indietro al ramo originale e lo resetta al commit originale, il che annullerà parte del lavoro che abbiamo fatto sopra).

Commentari (22)

Guarda qui per una spiegazione di base della testa staccata:

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

Linea di comando per visualizzarlo:

git branch

o

git branch -a

otterrete un output come quello che segue:

* (no branch)
master
branch1

Il * (nessun ramo) mostra che sei in testa staccata.

Avresti potuto arrivare a questo stato facendo un git checkout somecommit ecc. e ti avrebbe avvertito con quanto segue:

Sei in 'testa staccata'stato. Tu

puoi guardarti intorno, fare modifiche sperimentali modifiche e farne il commit, e puoi scartare qualsiasi commit che fai in questo stato senza impattare alcun ramo eseguendo un altro checkout.

Se vuoi creare un nuovo ramo per mantenere i commit che crei, puoi fare così (ora o più tardi) usando -b con il comando di checkout di nuovo. Esempio:

git checkout -b new_branch_name

Ora, per portarli su master:

Fai un git reflog o anche solo git log e nota i tuoi commit. Ora git checkout master e git merge i commit.

git merge HEAD@{1}

Modifica:

Per aggiungere, usa git rebase -i non solo per cancellare / uccidere i commit che non ti servono, ma anche per modificarli. Basta menzionare "edit" nella lista dei commit e sarai in grado di modificare il tuo commit e poi emettere un git rebase --continue per andare avanti. Questo avrebbe assicurato che non saresti mai arrivato in un HEAD staccato.

Commentari (2)

Prendi il tuo commit staccato nel suo proprio ramo

Esegui semplicemente git checkout -b mynewbranch.

Poi esegui git log, e vedrai che il commit è ora HEAD su questo nuovo ramo.

Commentari (2)