Risolvere un merge conflict in Git


Lezione 43 / 53

Risolvere un merge conflict in Git

Di Luca Ferretti


git guida git guida git italiano

Abbiamo accennato, in precedenza, alla possibilità che durante l’esecuzione di un git pull, git merge o git rebase in Git si possano verificare dei conflitti che impediscono a Git di portare a termine automaticamente l’operazione.

Un conflitto nell’accezione dei version control system è una situazione per la quale il sistema sta provando a integrare due diverse history che hanno entrambe apportato modifiche alle stesse righe di un file.

Il processo di merge può andare in uno stato di conflitto in due diversi momenti:

  • all’avvio del merge - avviene quando ci sono modifiche nella working copy o nella staging area; tali modifiche “pending” non possono essere gestite da Git e sarà compito dello sviluppatore “pulire” il proprio spazio di lavoro con git stash, git checkout, git commit o git reset
  • durante il merge - in tal caso il conflitto è effettivamente tra il branch in uso e quello che si sta mergiando, conflitto che riguarda modifiche apportate nei commit dei due branch.
     

NOTA: come visto nella precedente sezione, nel merge portiamo le modifiche presenti in un branch dentro al branch attualmente presente nella working copy. Nel seguito di questa sezione indicheremo tali branch rispettivamente come “incoming” e “current”.

Proviamo a fare qualche “esercizio” di merge conflict in Git. Proviamo a realizzare il caso in cui vogliamo riportare sul branch main delle modifiche fatte da noi su un branch, nella situazione in cui un altro sviluppatore web ha aggiunto un commit sul branch main successivamente alla creazione del nostro branch di sviluppo.
 

Merge conflict

Merge conflict
 

Creiamo un repository a cui aggiungiamo un file con un commit (con l’opzione --author per simulare meglio chi ha committato cosa).

$ mkdir git-merge-test
$ cd git-merge-test
$ git init .
$ echo "initial content to edit later" > merge.txt
$ git add merge.txt
$ git commit -m "initial content" --author="Someone <someone@example.com>"
[main (root-commit) 0a99708] initial content
Author: Someone <someone@example.com>
1 file changed, 1 insertion(+)
create mode 100644 merge.txt

Creiamo un nuovo branch, apportiamo modifiche al file e salviamo tali modifiche in un commit.

$ git checkout -b merge-me
Switched to a new branch 'merge-me'

$ echo "new content to merge later" > merge.txt
$ git commit -am "edited the content for conflict" --author "Me <me@example.com>"
[merge-me 15416df] edited the content for conflict
Author: Me <me@example.com>
1 file changed, 1 insertion(+), 1 deletion(-)

Torniamo sul branch main e aggiungiamo una nuova riga di testo al file, sempre poi salvando in un commit.

$ git checkout main
Switched to branch 'main'

$ echo "content added to previous" >> merge.txt
$ git commit -am "appended content to merge.txt" --author="Frank <frank@example.com>"
[main 90640eb] appended content to merge.txt
Author: Frank <frank@example.com>
1 file changed, 1 insertion(+)

Proviamo, quindi, a fare merge delle modifiche nel branch merge-me (incoming) nel branch main (current)

$ git merge merge-me
Auto-merging merge.txt
CONFLICT (content): Merge conflict in merge.txt
Automatic merge failed; fix conflicts and then commit the result.

$ git status
On branch main
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
        both modified:   merge.txt

no changes added to commit (use "git add" and/or "git commit -a")

Provando ad eseguire git merge viene segnalata la presenza di un conflitto. Il processo di merge si interrompe, lasciando la propria copia in uno stato di attesa di risoluzione.
 

Risolvere un merge conflict in Git

Con il comando git status possiamo evidenziare la presenza di un conflitto durante l’esecuzione di un merge. Vediamo come scoprire maggiori dettagli su cosa esattamente è cambiato tra le due versioni della history e come intervenire.

Nell’esempio di conflitto “forzato” sappiamo che il problema è nel file merge.txt, che ha ricevuto modifiche nelle stesse righe in entrambi i branch.

Nel momento in cui il merge si è interrotto, nel file in conflitto ci sono le modifiche provenienti da entrambe le versioni del file:

$ cat merge.txt
<<<<<<< HEAD
initial content to edit later
content added to previous
=======
new content to merge later
>>>>>>> merge-me

Di particolare interesse sono i marker di conflitto inseriti da Git che ci permettono di sapere che:

  • la righe tra <<<<<<< e ======= contengono le righe in conflitto nel file come erano nel commit del branch “current”
  • le righe tra ======= e >>>>>>> contengono le righe in conflitto nel file come erano nel commit del branch “incoming”.

Ci sono tre modi per risolvere i conflitti in un file e procedere con il merge, che ovviamente possono e debbono essere scelti caso per caso:

  • accettare in toto le modifiche che erano già nel branch “current” (nel nostro caso main) con git checkout --ours merge.txt
  • accettare in toto le modifiche che vengono dal branch “incoming” (nel nostro caso new_branch_to_merge_later) con git checkout --their merge.txt
  • modificare il file in modo che contenga un contenuto corretto e in linea con entrambe le modifiche, dichiarare che è stato risolto il conflitto con git add merge.txt e proseguire con il merge tramite un commit.

Se volessimo procedere con questa ultima opzione, dovremmo modificare il file fino a raggiungere un contenuto adeguato e poi:

$ cat merge.txt
new content to merge later
content added to previous

$ git status
On branch main
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
        both modified:   merge.txt

no changes added to commit (use "git add" and/or "git commit -a")

$ git add merge.txt

$ git status
On branch main
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:
modified:   merge.txt

Una volta effettuato il commit, la history del nostro repository sarà la seguente:

$ git log --pretty=format:'%h %s (%an)' --date=short  --graph
*   c9802c8 merged content due conflict (Me)
|\
| * 15416df edited the content for conflict (Me)
* | 90640eb appended content to merge.txt (Frank)
|/
* 0a99708 initial content (Someone)

Notare che in questo caso, il nostro commit iniziale sul branch è rimasto intatto (ce lo dice il fatto che abbia ancora lo stesso hash, 15416df) e le modifiche necessarie a risolvere il conflitto sono state incluse nel commit di merge.

Possiamo, infatti, confrontare la differenza tra gli ultimi due commit sul branch in uso con

$ git diff HEAD^1
diff --git a/merge.txt b/merge.txt
index c560b6f..732761b 100644
--- a/merge.txt
+++ b/merge.txt
@@ -1,2 +1,2 @@
-initial content to edit later
+new content to merge later
content added to previous

 

Guida Git in italiano 1 Che cos'è Git? 2 Nascita di Git 3 Principali caratteristiche di Git 4 Riga di comando e UI in Git 5 Come Installare Git 6 5 comandi Git per sviluppatori singoli 7 5 comandi Git per sviluppare collaborando 8 Repository in Git 9 Commit in Git 10 Working Copy in Git 11 Staging Area in Git 12 Branch in Git 13 Remote in Git 14 Inizializzare un nuovo repository con git init 15 Creare una copia di un repository remoto in Git con git clone 16 Configurare le opzioni di Git con git config 17 Il comando Git add in Git 18 Il comando Git commit in Git 19 Il comando Git diff in Git 20 Il comando Git stash in Git 21 .gitignore : i file ignored in Git 22 Il comando Git status in Git 23 il comando Git log in Git 24 Il comando Git tag in Git 25 Il comando Git blame in Git 26 Il comando Git checkout in Git 27 Il comando Git revert in Git 28 Il comando Git reset in Git 29 Il comando Git rm in Git 30 L'opzione Git commit –amend in Git 31 Git rebase –interactive in Git 32 Scorciatoie per comandi frequenti in Git 33 Repository condiviso in Git 34 Il modello Fork & pull 35 Il comando Git remote in Git 36 I principali repository remote di Git: Github, Gitlab e Bitbucket 37 Il comando Git fetch in Git 38 Il comando Git push in Git 39 Il comando Git pull in Git 40 Il comando Git branch in Git 41 Il comando Git checkout in Git 42 Il comando Git merge in Git 43 Risolvere un merge conflict in Git 44 Capire meglio il contenuto dei commit durante un conflitto di merge in Git 45 Workflow Git centralizzato 46 Workflow Git feature branching 47 Workflow Git trunk-based 48 Approccio “forking” in Git 49 Gitflow in Git 50 Messaggi di commit in Git 51 Tagging & Versioning in Git 52 L’opzione merge in Git 53 L’opzione rebase in Git
Scopri i corsi

Le nostre guide possono essere molto utili per muovere i primi passi nel mondo della programmazione, ma se vuoi iniziare una nuova carriera in ambito digital & tech con il supporto costante dei docenti e tantissime esercitazioni pratiche, ti consigliamo di frequentare uno dei corsi della nostra Hackademy!

Scopri i corsi