Git & Tricks – Pillole di source code management | Parte 3: l’importanza del rebase per un mondo migliore

9 months ago 261

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!

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.

Read Entire Article