Introduzione
Quando Git non può combinare automaticamente due modifiche alla stessa regione di un file, lascia marker di conflitto nella copia di lavoro e ti permette di risolvere manualmente. I marker non sono magia; registrano esattamente cosa ha fatto ogni lato relativo a una base comune.
Anatomia
<<<<<<< HEAD
riga come è sul nostro branch
=======
riga come è sul loro branch
>>>>>>> feature/login
La sezione tra <<< e === è "ours" (il branch corrente). Tra === e >>> è "theirs". La risoluzione consiste nel mantenere il testo giusto, cancellare i marker e mettere in stage.
Marker a tre vie (stile diff3)
git config --global merge.conflictStyle diff3
Ora i marker includono anche la versione base:
<<<<<<< HEAD
nostra versione
||||||| merged common ancestor
versione base
=======
loro versione
>>>>>>> feature/login
Vedere la base spesso chiarisce cosa intendeva ogni lato. Ancora meglio, da Git 2.35:
git config --global merge.conflictStyle zdiff3
che taglia il contesto comune.
Come Git li produce
Per ogni coppia di hunk in conflitto, Git esegue un diff a tre vie tra base, ours e theirs. Se entrambi i lati hanno cambiato la stessa regione diversamente, Git scrive entrambe le versioni circondate da marker. Altrimenti il lato che ha cambiato vince automaticamente.
Risolvere
git status # elenca path in conflitto
modifica ogni file
git add <file> # marca come risolto
git commit # usa il messaggio preparato
Oppure usa un merge tool:
git mergetool
git config --global merge.tool meld
Ispezionare ogni lato
git show :1:path # base
git show :2:path # ours
git show :3:path # theirs
git checkout --ours path
git checkout --theirs path
La sintassi colon legge direttamente dagli stage dell'index in conflitto.
Annullare
git merge --abort
git rebase --abort
git cherry-pick --abort
rerere
"Reuse recorded resolution" ricorda le tue risoluzioni di conflitto e le riapplica la prossima volta che lo stesso conflitto appare:
git config --global rerere.enabled true
Indispensabile per catene di rebase di lunga durata.
Conflitti di rename e binari
Non tutti i conflitti producono marker testuali. I conflitti rename/rename, rename/delete e modify/delete lasciano l'index in uno stato con più stage ma senza marker <<< nel file. I file binari non possono essere mergiati testualmente; entrambe le versioni vengono scritte su disco e l'index registra il conflitto:
git status
# both modified: image.png
# deleted by us: old.txt
# added by them: new.txt
git checkout --ours image.png # mantieni la nostra versione
git checkout --theirs new.txt
git add image.png new.txt
Leggi sempre git status al momento del conflitto; etichetta esplicitamente questi tipi di conflitto meno comuni.
Errori comuni
Risolvere cancellando i marker senza leggere i contenuti, accidentalmente eliminando un lato. Leggi sempre entrambe le metà e la base. Dimenticare git add dopo aver modificato; git commit rifiuterà con "unmerged paths". Trattare --ours e --theirs come identici a -X ours e -X theirs; il primo sostituisce un intero file in conflitto, il secondo è un suggerimento alla strategia di merge. Infine, copiare un file parzialmente risolto da altrove e bypassare Git; l'index ha ancora le entry stage 1/2/3 finché non fai git add.