Nella puntata precedente è stato affrontato il valore della coerenza nel gestire i commit relativi ai propri sviluppi, sottolineando come descrizioni chiare, estese e l’utilizzo di amend
possano fare la differenza nella gestione a lungo termine del proprio repository.
Rimanendo dello stesso ambito, questa puntata esplora l’importanza di una corretta gestione dei contributi, andando a sviscerare le differenze tra merge
e rebase
.
In cosa consiste un merge
Nello scorso articolo è stato esplorato il concetto di branch
, andando a creare quindi una variante (i fan di Loki capiranno di che cosa si sta parlando in un attimo) del ramo principale del repository, ossia main
.
Rimane quindi da capire quali sono le azioni da compiere per far sì che le variazioni presenti nel branch
alternativo diventino parte di quello principale. Rimane cioè da capire il concetto di merge
, ossia unione.
Dato un branch, in questo caso mio-contributo
, contenente una modifica che non esiste nel branch main
:
/git # git checkout mio-contributo
Switched to branch 'mio-contributo'
/git # git lg
* ab47204d465f - (2024-02-16 11:45:37 +0000) (HEAD -> mio-contributo) Settimo commit <Raoul Scarazzini>
* ebca67901328 - (2024-02-16 10:58:20 +0000) (main) Sesta modifica al repository <Raoul Scarazzini>
* d2a002445d21 - (2024-02-16 10:57:55 +0000) Quinto commit <Raoul Scarazzini>
* 667916b7af10 - (2024-02-16 10:57:55 +0000) Quarto commit <Raoul Scarazzini>
* ce337855b51c - (2024-02-16 10:57:55 +0000) Terzo commit <Raoul Scarazzini>
* bee7830d28ef - (2024-02-16 10:57:55 +0000) Secondo commit <Raoul Scarazzini>
* 1cb162db064c - (2024-02-16 10:57:55 +0000) Primo commit <Raoul Scarazzini>
/git # git checkout main
Switched to branch 'main'
/git # git lg
* ebca67901328 - (2024-02-16 10:58:20 +0000) (HEAD -> main) Sesta modifica al repository <Raoul Scarazzini>
* d2a002445d21 - (2024-02-16 10:57:55 +0000) Quinto commit <Raoul Scarazzini>
* 667916b7af10 - (2024-02-16 10:57:55 +0000) Quarto commit <Raoul Scarazzini>
* ce337855b51c - (2024-02-16 10:57:55 +0000) Terzo commit <Raoul Scarazzini>
* bee7830d28ef - (2024-02-16 10:57:55 +0000) Secondo commit <Raoul Scarazzini>
* 1cb162db064c - (2024-02-16 10:57:55 +0000) Primo commit <Raoul Scarazzini>
Per far sì che questa ne diventi parte si potrà usare il comando git merge
, passando come parametro il nome del branch che si vuole unire, in questo caso quindi mio-contributo
:
/git # git merge mio-contributo
Updating ebca67901328..ab47204d465f
Fast-forward
Settimo.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 Settimo.txt
Da questo momento le modifiche che sono presenti nel branch alternativo saranno parte anche del branch principale:
/git # git lg
* ab47204d465f - (2024-02-16 11:45:37 +0000) (HEAD -> main, mio-contributo) Settimo commit <Raoul Scarazzini>
* ebca67901328 - (2024-02-16 10:58:20 +0000) Sesta modifica al repository <Raoul Scarazzini>
* d2a002445d21 - (2024-02-16 10:57:55 +0000) Quinto commit <Raoul Scarazzini>
* 667916b7af10 - (2024-02-16 10:57:55 +0000) Quarto commit <Raoul Scarazzini>
* ce337855b51c - (2024-02-16 10:57:55 +0000) Terzo commit <Raoul Scarazzini>
* bee7830d28ef - (2024-02-16 10:57:55 +0000) Secondo commit <Raoul Scarazzini>
* 1cb162db064c - (2024-02-16 10:57:55 +0000) Primo commit <Raoul Scarazzini>
HEAD
è quindi riferimento comune per entrambi i branch
. Preciso, pulito, semplicissimo… In un mondo perfetto, denso di unicorni e arcobaleni.
Gestione dei conflitti
I branch
e le varianti in esso presenti fintanto che operano su file diversi possono funzionare in maniera molto lineare, ma cosa succede quando le varianti operano sugli stessi file? Come si gestiscono i conflitti?
Ripartendo dalla precedente situazione iniziale:
/git # git checkout mio-contributo
Already on 'mio-contributo'
/git # git lg
* 4fa4cd79e977 - (2024-02-16 13:41:29 +0000) (HEAD -> mio-contributo) Settimo commit <Raoul Scarazzini>
* ebca67901328 - (2024-02-16 10:58:20 +0000) (main) Sesta modifica al repository <Raoul Scarazzini>
* d2a002445d21 - (2024-02-16 10:57:55 +0000) Quinto commit <Raoul Scarazzini>
* 667916b7af10 - (2024-02-16 10:57:55 +0000) Quarto commit <Raoul Scarazzini>
* ce337855b51c - (2024-02-16 10:57:55 +0000) Terzo commit <Raoul Scarazzini>
* bee7830d28ef - (2024-02-16 10:57:55 +0000) Secondo commit <Raoul Scarazzini>
* 1cb162db064c - (2024-02-16 10:57:55 +0000) Primo commit <Raoul Scarazzini>
/git # git checkout main
Switched to branch 'main'
/git # git lg
* ebca67901328 - (2024-02-16 10:58:20 +0000) (HEAD -> main) Sesta modifica al repository <Raoul Scarazzini>
* d2a002445d21 - (2024-02-16 10:57:55 +0000) Quinto commit <Raoul Scarazzini>
* 667916b7af10 - (2024-02-16 10:57:55 +0000) Quarto commit <Raoul Scarazzini>
* ce337855b51c - (2024-02-16 10:57:55 +0000) Terzo commit <Raoul Scarazzini>
* bee7830d28ef - (2024-02-16 10:57:55 +0000) Secondo commit <Raoul Scarazzini>
* 1cb162db064c - (2024-02-16 10:57:55 +0000) Primo commit <Raoul Scarazzini>
All’interno del branch main
si andrà ad aggiungere un commit che modifica Settimo.txt
, lo stesso file modificato nel branch mio-contributo
:
/git # git checkout main
Switched to branch 'main'
/git # git branch
* main
mio-contributo
/git # echo "Variante settimo commit" > Settimo.txt
/git # git add Settimo.txt && git commit -m "Variante settimo commit" -m "Descrizione estesa della variante sul settimo commit"
[main 0b3f76b1f184] Variante settimo commit
1 file changed, 1 insertion(+)
/git # git lg
* 4d234c62e6a2 - (2024-02-16 13:44:15 +0000) (HEAD -> main) Variante settimo commit <Raoul Scarazzini>
* ebca67901328 - (2024-02-16 10:58:20 +0000) Sesta modifica al repository <Raoul Scarazzini>
* d2a002445d21 - (2024-02-16 10:57:55 +0000) Quinto commit <Raoul Scarazzini>
* 667916b7af10 - (2024-02-16 10:57:55 +0000) Quarto commit <Raoul Scarazzini>
* ce337855b51c - (2024-02-16 10:57:55 +0000) Terzo commit <Raoul Scarazzini>
* bee7830d28ef - (2024-02-16 10:57:55 +0000) Secondo commit <Raoul Scarazzini>
* 1cb162db064c - (2024-02-16 10:57:55 +0000) Primo commit <Raoul Scarazzini>
La situazione è quindi di conflitto.
Se si provasse ad effettuare un merge del branch mio-contributo
all’interno del branch main
questi fallirebbe. In queste situazioni quindi è necessario sanare manualmente i conflitti, modificando la propria prospettiva. È infatti all’interno del branch variante mio-contributo
che si opererà, in modo da renderlo compatibile con un merge “pulito” come quello mostrato in apertura.
Ci sono sostanzialmente due modi per raggiungere il risultato: sanare i conflitti mediante merge
oppure mediante rebase
.
Utilizzo di merge
L’allineamento del branch mio-contributo
con main
utilizzando merge
rivelerà cosa c’è da risolvere:
/git # git checkout mio-contributo
Switched to branch 'mio-contributo'
/git # git merge main
Auto-merging Settimo.txt
CONFLICT (add/add): Merge conflict in Settimo.txt
Automatic merge failed; fix conflicts and then commit the result.
/git # git status
On branch mio-contributo
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 added: Settimo.txt
no changes added to commit (use "git add" and/or "git commit -a")
Il problema è chiaro: entrambi i commit hanno modificato lo stesso file, quindi va gestito il conflitto che, all’interno del file apparirà evidenziato così:
/git # cat Settimo.txt
<<<<<<< HEAD
Settimo commit
=======
Variante settimo commit
>>>>>>> main
si capisce come la porzione inclusa tra <<<<<<< HEAD
e =======
sia relativa alle modifiche del branch attuale, mentre l’altra, da =======
a >>>>>>> main
sia relativa a quanto vogliamo includere.
Una volta ricomposto il file secondo le aspettative, ad esempio in questo modo:
Settimo commit e Variante settimo commit
Sarà possibile completare il merge:
/git # git add Settimo.txt
/git # git merge --continue
(vim terminal opens)
L’operazione comporterà l’apertura dell’editor vim, che consentirà di inserire un messaggio esteso per il commit di merge:
Merge branch 'main' into mio-contributo
Modifica cumulativa al file Settimo.txt.
# Conflicts:
# Settimo.txt
#
# It looks like you may be committing a merge.
# If this is not correct, please run
# git update-ref -d MERGE_HEAD
# and try again.
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch mio-contributo
# All conflicts fixed but you are still merging.
#
# Changes to be committed:
# modified: Settimo.txt
#
Salvando ed uscendo dall’editor (:wq
) un nuovo commit (il merge, appunto) verrà aggiunto nella sequenza:
/git # git merge --continue
[mio-contributo d37d249c3922] Merge branch 'main' into mio-contributo
/git # git lg
* ba0ce8b41ad4 - (2024-02-16 13:59:53 +0000) (HEAD -> mio-contributo) Merge branch 'main' into mio-contributo <Raoul Scarazzini>
|\
| * 4d234c62e6a2 - (2024-02-16 13:44:15 +0000) (main) Variante settimo commit <Raoul Scarazzini>
* | 4fa4cd79e977 - (2024-02-16 13:41:29 +0000) Settimo commit <Raoul Scarazzini>
|/
* ebca67901328 - (2024-02-16 10:58:20 +0000) Sesta modifica al repository <Raoul Scarazzini>
* d2a002445d21 - (2024-02-16 10:57:55 +0000) Quinto commit <Raoul Scarazzini>
* 667916b7af10 - (2024-02-16 10:57:55 +0000) Quarto commit <Raoul Scarazzini>
* ce337855b51c - (2024-02-16 10:57:55 +0000) Terzo commit <Raoul Scarazzini>
* bee7830d28ef - (2024-02-16 10:57:55 +0000) Secondo commit <Raoul Scarazzini>
* 1cb162db064c - (2024-02-16 10:57:55 +0000) Primo commit <Raoul Scarazzini>
L’alberatura ha subito una variazione, ma ora comprende anche il commit presente nel branch main
, pertanto il merge del branch mio-contributo
nel branch main
avverrà senza colpo ferire:
/git # git checkout main
Switched to branch 'main'
/git # git merge mio-contributo
Updating 4d234c62e6a2..ba0ce8b41ad4
Fast-forward
Settimo.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
/git # git lg
* ba0ce8b41ad4 - (2024-02-16 13:59:53 +0000) (HEAD -> main, mio-contributo) Merge branch 'main' into mio-contributo <Raoul Scarazzini>
|\
| * 4d234c62e6a2 - (2024-02-16 13:44:15 +0000) Variante settimo commit <Raoul Scarazzini>
* | 4fa4cd79e977 - (2024-02-16 13:41:29 +0000) Settimo commit <Raoul Scarazzini>
|/
* ebca67901328 - (2024-02-16 10:58:20 +0000) Sesta modifica al repository <Raoul Scarazzini>
* d2a002445d21 - (2024-02-16 10:57:55 +0000) Quinto commit <Raoul Scarazzini>
* 667916b7af10 - (2024-02-16 10:57:55 +0000) Quarto commit <Raoul Scarazzini>
* ce337855b51c - (2024-02-16 10:57:55 +0000) Terzo commit <Raoul Scarazzini>
* bee7830d28ef - (2024-02-16 10:57:55 +0000) Secondo commit <Raoul Scarazzini>
* 1cb162db064c - (2024-02-16 10:57:55 +0000) Primo commit <Raoul Scarazzini>
HEAD
è ora allineato ad entrambi i branch, che quindi sono identici, anche se esteticamente la situazione lascia un poco a desiderare in termini di comprensione.
Utilizzo di rebase
Considerando la stessa situazione di partenza, lo stesso processo può essere eseguito utilizzando rebase
:
/git # git checkout main
Switched to branch 'main'
/git # git lg
* 6474fc1a2e20 - (2024-02-16 11:08:23 +0000) (HEAD -> main) Variante settimo commit <Raoul Scarazzini>
* ebca67901328 - (2024-02-16 10:58:20 +0000) Sesta modifica al repository <Raoul Scarazzini>
* d2a002445d21 - (2024-02-16 10:57:55 +0000) Quinto commit <Raoul Scarazzini>
* 667916b7af10 - (2024-02-16 10:57:55 +0000) Quarto commit <Raoul Scarazzini>
* ce337855b51c - (2024-02-16 10:57:55 +0000) Terzo commit <Raoul Scarazzini>
* bee7830d28ef - (2024-02-16 10:57:55 +0000) Secondo commit <Raoul Scarazzini>
* 1cb162db064c - (2024-02-16 10:57:55 +0000) Primo commit <Raoul Scarazzini>
/git # git checkout mio-contributo
Switched to branch 'mio-contributo'
/git # git lg
* 1c6886c1eff5 - (2024-02-16 10:59:21 +0000) (HEAD -> mio-contributo, main) Settimo commit <Raoul Scarazzini>
* ebca67901328 - (2024-02-16 10:58:20 +0000) Sesta modifica al repository <Raoul Scarazzini>
* d2a002445d21 - (2024-02-16 10:57:55 +0000) Quinto commit <Raoul Scarazzini>
* 667916b7af10 - (2024-02-16 10:57:55 +0000) Quarto commit <Raoul Scarazzini>
* ce337855b51c - (2024-02-16 10:57:55 +0000) Terzo commit <Raoul Scarazzini>
* bee7830d28ef - (2024-02-16 10:57:55 +0000) Secondo commit <Raoul Scarazzini>
* 1cb162db064c - (2024-02-16 10:57:55 +0000) Primo commit <Raoul Scarazzini>
Dal branch mio-contributo
anziché merge
si effettuerà un rebase
di main
:
/git # git rebase main
Auto-merging Settimo.txt
CONFLICT (add/add): Merge conflict in Settimo.txt
error: could not apply 1c6886c1eff5... Settimo commit
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 1c6886c1eff5... Settimo commit
/git # git status
interactive rebase in progress; onto 6474fc1a2e20
Last command done (1 command done):
pick 1c6886c1eff5 Settimo commit
No commands remaining.
You are currently rebasing branch 'mio-contributo' on '6474fc1a2e20'.
(fix conflicts and then run "git rebase --continue")
(use "git rebase --skip" to skip this patch)
(use "git rebase --abort" to check out the original branch)
Unmerged paths:
(use "git restore --staged <file>..." to unstage)
(use "git add <file>..." to mark resolution)
both added: Settimo.txt
no changes added to commit (use "git add" and/or "git commit -a")
Anche in questo caso esiste lo stesso conflitto (non potrebbe essere altrimenti), e si può gestire in maniera identica al precedente, sistemando il contenuto del file Settimo.txt
:
/git # cat Settimo.txt
<<<<<<< HEAD
Variante settimo commit
=======
Settimo commit
>>>>>>> 1c6886c1eff5 (Settimo commit)
/git # vim Settimo.txt
(vim terminal opens)
/git # cat Settimo.txt
Settimo commit e Variante settimo commit
E continuando l’operazione di rebase
:
/git # git add Settimo.txt
/git # git rebase --continue
(vim terminal opens)
La differenza sostanziale è che ad essere creato sarà un nuovo commit, basato sul precedente, e non un commit di merge:
Settimo commit
Descrizione estesa del settimo commit
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# interactive rebase in progress; onto 6474fc1a2e20
# Last command done (1 command done):
# pick 9d9d72d6709f Settimo commit
# No commands remaining.
# You are currently rebasing branch 'mio-contributo' on '6474fc1a2e20'.
#
# Changes to be committed:
# modified: Settimo.txt
#
Il che comporterà, una volta completato l’editing ed usciti dal terminale vim
, questa sequenza di commit:
/git # git rebase --continue
[detached HEAD 9d9d72d6709f] Settimo commit
1 file changed, 4 insertions(+)
Successfully rebased and updated refs/heads/mio-contributo.
/git # git lg
* 9d9d72d6709f - (2024-02-16 11:12:03 +0000) (HEAD -> mio-contributo) Settimo commit <Raoul Scarazzini>
* 6474fc1a2e20 - (2024-02-16 11:08:23 +0000) (main) Variante settimo commit <Raoul Scarazzini>
* ebca67901328 - (2024-02-16 10:58:20 +0000) Sesta modifica al repository <Raoul Scarazzini>
* d2a002445d21 - (2024-02-16 10:57:55 +0000) Quinto commit <Raoul Scarazzini>
* 667916b7af10 - (2024-02-16 10:57:55 +0000) Quarto commit <Raoul Scarazzini>
* ce337855b51c - (2024-02-16 10:57:55 +0000) Terzo commit <Raoul Scarazzini>
* bee7830d28ef - (2024-02-16 10:57:55 +0000) Secondo commit <Raoul Scarazzini>
* 1cb162db064c - (2024-02-16 10:57:55 +0000) Primo commit <Raoul Scarazzini>
Rispetto al risultato ottenuto in precedenza con il merge
, la lista dei commit è decisamente più lineare, e sarà anche più facilmente integrabile nel branch main
:
/git # git checkout main
Switched to branch 'main'
/git # git merge mio-contributo
Updating 6474fc1a2e20..9d9d72d6709f
Fast-forward
Settimo.txt | 4 ++++
1 file changed, 4 insertions(+)
/git # git lg
* 9d9d72d6709f - (2024-02-16 11:12:03 +0000) (HEAD -> main, mio-contributo) Settimo commit <Raoul Scarazzini>
* 6474fc1a2e20 - (2024-02-16 11:08:23 +0000) Variante settimo commit <Raoul Scarazzini>
* ebca67901328 - (2024-02-16 10:58:20 +0000) Sesta modifica al repository <Raoul Scarazzini>
* d2a002445d21 - (2024-02-16 10:57:55 +0000) Quinto commit <Raoul Scarazzini>
* 667916b7af10 - (2024-02-16 10:57:55 +0000) Quarto commit <Raoul Scarazzini>
* ce337855b51c - (2024-02-16 10:57:55 +0000) Terzo commit <Raoul Scarazzini>
* bee7830d28ef - (2024-02-16 10:57:55 +0000) Secondo commit <Raoul Scarazzini>
* 1cb162db064c - (2024-02-16 10:57:55 +0000) Primo commit <Raoul Scarazzini>
Come si può notare nella lista dei commit HEAD
corrisponde tanto al branch main
quanto a mio-contributo
. Tutte le variazioni sono state mantenute, ma a differenza della situazione ottenuta con il merge
, il rebase
in questo caso garantisce molta più linearità, garantendo anche una gestione dei commit più mirata, anche in caso di annullamento delle modifiche, ad esempio mediante git revert
.
Conclusioni
Quanto sia preziosa una lista di commit lineare rispetto ad una intersecata e con mille diramazioni è letteralmente incalcolabile. Il tutto concorre ancora una volta al tema principale che aleggia questa serie: l’empatia. Mettersi nei panni di chi dovrà un domani gestire il repository, spendendo qualche minuto in più per rendere la vita più facile in termini di comprensione e gestione, fa di noi delle persone migliori.
Alla prossima puntata!
La serie sino ad ora
Git & Tricks – Pillole di source code management | Parte 1: un ambiente confortevole
Git & Tricks – Pillole di source code management | Parte 2: gestire i commit con empatia
Da sempre appassionato del mondo open-source e di Linux nel 2009 ho fondato il portale Mia Mamma Usa Linux! per condividere articoli, notizie ed in generale tutto quello che riguarda il mondo del pinguino, con particolare attenzione alle tematiche di interoperabilità, HA e cloud.
E, sì, mia mamma usa Linux dal 2009.
Lascia un commento