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.
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
producerefs/heads/master
. Il ramo chiamato "master" viene controllato.git rev-parse refs/heads/master
produce17a02998078923f2d62811326d130de991d1a95a
Quel commit è l'attuale punta o "testa" del ramo master.git rev-parse HEAD
produce17a02998078923f2d62811326d130de991d1a95a
. 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
HEAD
→refs/heads/master
→17a02998078923f2d62811326d130de991d1a95a
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 confatal: ref HEAD is not a symbolic ref
.git rev-parse HEAD
produce17a02998078923f2d62811326d130de991d1a95a
. Poiché non è un ref simbolico, deve puntare direttamente al commit stesso.Abbiamo
HEAD
→17a02998078923f2d62811326d130de991d1a95a
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:
(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:
(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:(questi due comandi possono essere abbreviati come
git checkout -B master temp
);Puoi quindi cancellare il ramo temporaneo:
Infine, probabilmente vorrai fare un push della storia ristabilita:
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 userestigit 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).Guarda qui per una spiegazione di base della testa staccata:
http://git-scm.com/docs/git-checkout
Linea di comando per visualizzarlo:
o
otterrete un output come quello che segue:
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
Ora, per portarli su master:
Fai un
git reflog
o anche sologit log
e nota i tuoi commit. Oragit checkout master
egit merge
i commit.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 ungit rebase --continue
per andare avanti. Questo avrebbe assicurato che non saresti mai arrivato in un HEAD staccato.Prendi il tuo commit staccato nel suo proprio ramo
Esegui semplicemente
git checkout -b mynewbranch
.Poi esegui
git log
, e vedrai che il commit è oraHEAD
su questo nuovo ramo.