W3docs

strategie di merge

Scopri le strategie di merge Git — ort, recursive, resolve, octopus, ours e subtree — più fast-forward, squash e merge espliciti, con esempi.

mergeconflicts

Strategie di merge in Git

Quando il lavoro su un branch è completo e pronto per essere unito alla linea principale di sviluppo, Git deve decidere come combinare le due storie. L'algoritmo che utilizza per farlo si chiama strategia di merge.

Una strategia di merge prende due (o più) tip di branch e produce un singolo risultato. La maggior parte delle volte non si nomina mai una strategia in modo esplicito — il comando git merge sceglie un valore predefinito sensato in base a quanti branch si stanno unendo e se le loro storie hanno divergito. Quando hai bisogno di controllo, usa -s <strategy> (e facoltativamente -X <strategy-option> per una regolazione fine):

git merge -s recursive feature

Questa pagina copre due idee correlate che è facile confondere:

  • Strategie di merge (-s): l'algoritmo che calcola l'albero unito — ort, recursive, resolve, octopus, ours, subtree.
  • Tipi di merge: il tipo di risultato che si ottiene — un commit di merge esplicito, un fast-forward, o uno squash.

Quasi mai si ha bisogno di scegliere una strategia manualmente. Le impostazioni predefinite sono corrette per la stragrande maggioranza dei merge; ricorri a -s o -X solo quando incappi in un problema specifico che risolvono.

Algoritmi delle strategie di merge

ort (predefinito)

git merge -s ort feature

ort ("Ostensibly Recursive's Twin") è la strategia di merge predefinita a due teste da Git 2.34 (2021). È una riscrittura più veloce e più corretta della vecchia strategia recursive e produce lo stesso tipo di risultato: un merge a 3 vie che gestisce i rinominamenti e unisce ricorsivamente più antenati comuni in un singolo antenato virtuale.

Poiché è il valore predefinito, si ottiene ort automaticamente quando si esegue un merge semplice:

git checkout main
git merge feature

recursive

git merge -s recursive feature

La strategia a 3 vie originale per l'unione di due branch, e il valore predefinito prima di Git 2.34. Può rilevare e seguire i rinominamenti ma non può usare copie di file rilevate. Raramente è necessario richiederla per nome ora — ort la sostituisce — ma è ancora disponibile per compatibilità.

resolve

git merge -s resolve feature

resolve esegue un singolo merge a 3 vie tra esattamente due teste (il branch corrente e quello nominato). Non tenta di essere intelligente riguardo a più basi di merge, il che lo rende veloce e prevedibile, ma può eseguire merge errati attraverso una storia "criss-cross" in cui due branch sono stati uniti l'uno nell'altro in precedenza. Usalo solo quando il merge recursive/ort produce un risultato che si vuole verificare con un algoritmo più semplice.

octopus

git merge -s octopus topic-a topic-b topic-c

octopus è la strategia predefinita quando si uniscono più di due branch contemporaneamente. Raccoglie diversi tip di branch in un unico commit di merge, il che è pratico per unire un insieme di branch topic indipendenti. Rifiuta deliberatamente qualsiasi merge che richiederebbe una risoluzione manuale dei conflitti — i merge octopus sono pensati per essere puliti, quindi in caso di conflitto è necessario unire i branch singolarmente.

ours

git merge -s ours obsolete-branch

La strategia ours registra un commit di merge che ha l'altro branch come genitore ma mantiene l'albero del branch corrente completamente invariato — ogni modifica dall'altro branch viene scartata. L'uso tipico è marcare un branch come "unito" per la storia (in modo che i futuri merge lo conoscano) ignorandone il contenuto effettivo, ad esempio quando si archivia un branch longevo il cui lavoro non è più desiderato.

Attenzione

Non confondere la strategia ours (-s ours, che scarta completamente il contenuto dell'altro branch) con l'opzione di strategia ours (-X ours, che mantiene il proprio lato solo per le righe che effettivamente generano conflitti). Si comportano in modo molto diverso.

subtree

git merge -s subtree project-b

subtree è una variante dell'algoritmo recursive/ort per il caso in cui un albero è una sottodirectory (un "subtree") dell'altro. Prima di unire, Git sposta i percorsi di un albero in modo che i due si allineino, poi unisce normalmente. Questo è il meccanismo alla base dell'incorporazione di un progetto in una sottocartella di un altro. Per il lavoro quotidiano con i subtree, il comando di livello superiore git subtree è solitamente più comodo.

Tipi di merge: come appare il risultato

La strategia decide come gli alberi vengono combinati; il tipo di merge descrive la forma della storia che ne risulta.

Merge fast-forward

Quando il branch in cui si sta eseguendo il merge non si è mosso da quando l'altro branch è stato creato, non c'è nulla da combinare — Git può semplicemente spostare in avanti il puntatore del branch all'ultimo commit. Non viene creato nessun nuovo commit e la storia rimane perfettamente lineare. Questo è il comportamento predefinito di Git ogni volta che è possibile:

git checkout main
git merge feature
# Output (when main is an ancestor of feature):
# Updating a1b2c3d..d4e5f6a
# Fast-forward
#  app.js | 3 +++
#  1 file changed, 3 insertions(+)

Per mantenere un registro esplicito del merge anche quando un fast-forward è possibile, si forza un commit di merge con --no-ff:

git merge --no-ff feature

Commit di merge esplicito (a 3 vie)

Quando entrambi i branch hanno nuovi commit — le loro storie hanno divergito — Git crea un nuovo commit di merge con due genitori. È "esplicito" perché il commit è visibile nella storia e registra esattamente dove e quando i branch si sono uniti:

git checkout main
git merge feature
# Output (when histories diverged):
# Merge made by the 'ort' strategy.
#  app.js | 5 +++++
#  1 file changed, 5 insertions(+)

Se i due branch hanno modificato le stesse righe, il merge si interrompe con un conflitto che deve essere risolto manualmente — vedi conflitti di merge.

Squash merge

Uno squash merge comprime tutti i commit del branch sorgente in un unico nuovo commit sul branch corrente. Non crea un commit di merge e non registra il branch sorgente come genitore, quindi i singoli commit del branch sorgente non compaiono mai nella storia di destinazione:

git checkout main
git merge --squash feature
# Changes are staged but NOT committed yet:
git commit -m "Add feature X"

Questo mantiene la storia del branch principale ordinata — un commit per funzionalità — al costo di perdere il log dettagliato dei commit del branch della funzionalità. È una politica popolare per le pull request. Per riscrivere i commit all'interno di un branch, vedi il rebase interattivo.

Opzioni di strategia (-X)

Le strategie ort/recursive accettano opzioni aggiuntive tramite il flag -X (nota la X maiuscola, separata da -s). Ad esempio, per risolvere automaticamente i conflitti a favore del proprio lato:

git merge -X ours feature

Le opzioni disponibili sono:

OpzioneEffetto
oursRisolve automaticamente gli hunk in conflitto favorendo il proprio lato. Le modifiche non in conflitto dall'altro albero vengono comunque unite. (A differenza di -s ours, che scarta completamente l'altro lato.)
theirsL'opposto di ours: risolve automaticamente i conflitti a favore dell'altro albero. Non esiste una strategia theirs separata, solo questa opzione.
patienceDedica più tempo alla corrispondenza delle righe per evitare merge errati causati da righe corrispondenti non importanti.
diff-algorithm=<algo>Indica al merge di usare un algoritmo diff diverso (ad es. histogram, minimal, patience).
ignore-space-change / ignore-all-spaceIgnora le differenze solo di spazi bianchi nel rilevamento dei conflitti. Le modifiche agli spazi bianchi miste a modifiche reali non vengono ignorate.
renormalizeEsegue un check-out e check-in virtuale di tutti gli stage dei file, utile quando sono cambiati i filtri di fine riga o smudge/clean.
no-renormalizeDisabilita l'opzione renormalize.
no-renamesDisattiva il rilevamento dei rinominamenti durante il merge.
find-renames=<n>Attiva il rilevamento dei rinominamenti con una soglia di similarità di n% (predefinito 50%).
subtree=<path>Come la strategia subtree, ma consente di specificare il prefisso del percorso da spostare per allineare gli alberi.

Come scegliere

Per il lavoro quotidiano non si sceglie affatto una strategia — si lascia che Git usi ort e si decide solo il risultato desiderato:

  • Si vuole la storia più semplice e lineare quando possibile? Basta git merge (i fast-forward avvengono automaticamente).
  • Si vuole che ogni merge sia registrato come un commit? Aggiungere --no-ff.
  • Si vuole un unico commit pulito per funzionalità su main? Usare --squash.
  • Si stanno unendo diversi branch topic completati contemporaneamente? Il semplice git merge a b c usa octopus.

Se si preferisce ripetere i propri commit sopra il branch di destinazione invece di fare un merge, vedi git rebase. Per portare un singolo commit da un altro branch, usa git cherry-pick.

Esercitazione

Pratica
What are the different merge strategies in Git and their characteristics?
What are the different merge strategies in Git and their characteristics?
Was this page helpful?