Introduzione
Git tiene diversi ref speciali "ausiliari" in .git/ per registrare lo stato durante operazioni multi-step. Conoscerli trasforma recuperi spaventosi in one-liner.
ORIG_HEAD
ORIG_HEAD viene impostato ogni volta che un'operazione "pericolosa" sposta HEAD di molto: merge, rebase, reset, am. Cattura la punta precedente così puoi disfare:
git merge feature
# decidi che era un errore
git reset --hard ORIG_HEAD
Stesso trucco dopo un brutto rebase o reset:
git rebase main
git reset --hard ORIG_HEAD # torna allo stato pre-rebase
MERGE_HEAD
Durante un merge, MERGE_HEAD registra la punta del branch in fase di merge. Esiste dall'inizio di git merge finché il merge commit non viene creato o il merge non viene annullato:
git merge feature
cat .git/MERGE_HEAD
git merge --abort # rimuove MERGE_HEAD
Se hai finito di risolvere i conflitti e vuoi scrivere il merge commit:
git commit # usa MERGE_HEAD per registrare il secondo parent
MERGE_MSG e AUTO_MERGE
I file compagni includono .git/MERGE_MSG (il messaggio di commit preparato) e, da Git 2.40, AUTO_MERGE (un tree contenente la migliore auto-risoluzione di Git). Quest'ultimo è utile per fare diff contro la tua risoluzione manuale.
CHERRY_PICK_HEAD e REVERT_HEAD
Durante un cherry-pick o revert, Git registra il commit di origine:
git cherry-pick a1b2c3d
cat .git/CHERRY_PICK_HEAD
git cherry-pick --abort
git cherry-pick --continue # dopo aver risolto i conflitti
Lo stesso pattern si applica a git revert con REVERT_HEAD.
BISECT_HEAD e REBASE_HEAD
Quando bisecting o rebasing, Git scrive anche BISECT_HEAD o REBASE_HEAD per tracciare lo stato in corso. Strumenti come git status li usano per stampare messaggi accurati "sei nel mezzo di X".
FETCH_HEAD
Dopo un fetch, FETCH_HEAD elenca cosa è stato scaricato, un ref per riga. git pull lo legge per sapere cosa mergiare:
git fetch origin main
cat .git/FETCH_HEAD
git merge FETCH_HEAD
Mettere insieme
git status # fa riferimento a tutti questi quando serve
ls .git/*HEAD # elenca i ref ausiliari correnti
Rilevare operazioni in corso
git status legge questi ref ausiliari per dirti cosa è in corso. Gli script possono fare lo stesso testando l'esistenza dei file:
if test -f "$(git rev-parse --git-dir)/MERGE_HEAD"; then
echo "merge in corso"
fi
if test -f "$(git rev-parse --git-dir)/CHERRY_PICK_HEAD"; then
echo "cherry-pick in corso"
fi
if test -d "$(git rev-parse --git-dir)/rebase-merge"; then
echo "rebase interattivo in corso"
fi
Questo è esattamente ciò che fanno le integrazioni di prompt della shell come __git_ps1 per renderizzare (main|MERGING).
Errori comuni
Cercare di cancellare MERGE_HEAD manualmente per uscire da un merge; usa git merge --abort. Dimenticare che ORIG_HEAD esiste e ricreare commit a mano dopo un brutto reset. Confondere FETCH_HEAD (ultimo fetch, transitorio) con i ref di tracking remoto come origin/main (persistenti). E infine, fare script attorno a questi ref assumendo che esistano; MERGE_HEAD esiste solo durante un merge, quindi testa sempre il file prima di leggerlo.