Introduzione
Git ha due esiti di merge fondamentalmente diversi: fast-forward, dove la punta di un branch viene semplicemente spostata, e a tre vie, dove viene creato un nuovo commit con due parent. La scelta dipende dal fatto che i branch siano divergenti.
Fast-forward
Se la punta di main è un antenato di feature, nessun vero merge è necessario. Git sposta semplicemente main avanti:
A---B---C feature
/
main
# dopo git merge feature
A---B---C main, feature
Comando:
git switch main
git merge feature
Per forzare un vero merge commit anche quando il fast-forward è possibile:
git merge --no-ff feature
Merge a tre vie
Quando entrambi i branch sono divergenti, Git trova la merge base (l'antenato comune più recente) e combina le modifiche da entrambi i lati:
A---B---C feature
/
A---D---E main
# dopo git merge feature
A---D---E---M main
\ /
B---C feature
La strategia di merge predefinita di Git da 2.33 è ort; prima era recursive. Entrambe calcolano un merge a tre vie per file usando la merge base.
Ispezionare
git merge-base main feature
git log --oneline --graph --all
git show --stat HEAD # vedi il merge commit
Strategie e opzioni
git merge -X ours feature # preferisci il nostro lato in caso di conflitti
git merge -X theirs feature # preferisci il loro lato
git merge -X ignore-space-change feature
git merge --squash feature # applica modifiche senza merge commit
--squash crea un singolo commit che contiene tutte le modifiche da feature, senza link parent ad esso. Utile per una storia ordinata ma perde il dettaglio per-commit.
Annullare e disfare
git merge --abort # a metà merge, torna allo stato pre-merge
git reset --hard ORIG_HEAD # dopo un merge finito, disfalo
Configurare la politica fast-forward
git config --global merge.ff false # crea sempre merge commit
git config --global merge.ff only # solo fast-forward, rifiuta altrimenti
git config --global pull.ff only # uguale per i pull
Driver di merge e attributi
Alcuni file mergiano male con il three-way testuale (lockfile generati da macchine, documentazione generata). Configura un driver di merge personalizzato tramite .gitattributes e git config:
# .gitattributes
package-lock.json merge=ours
Gemfile.lock merge=union
git config merge.ours.driver true
git config merge.union.name "Line union merge"
git config merge.union.driver "git merge-file --union %A %O %B"
La strategia integrata union mantiene entrambi i lati; ours sceglie sempre la nostra versione; i driver personalizzati possono eseguire qualsiasi programma. Usa con parsimonia; comportamenti di merge sorprendenti confondono i revisori.
Errori comuni
Credere che il fast-forward sia in qualche modo speciale o rischioso; non lo è, è solo uno spostamento di puntatore. Usare --no-ff su ogni merge in un progetto a movimento veloce, inquinando la storia con merge commit vuoti. Usare -X ours quando intendevi --strategy=ours (che scarta interamente l'altro lato) o viceversa. Infine, modificare manualmente il messaggio del merge commit dopo il fatto e rompere il wording standardizzato "Merge branch 'X'" su cui gli strumenti fanno affidamento; se devi modificare, fallo prima di git commit.