“Commit early, commit often” è un mantra popolare nello sviluppo software quando si utilizza Git. Fare così garantisce che ogni cambiamento sia ben documentato, migliora la collaborazione e rende più facile monitorare l’evoluzione del progetto. Tuttavia, questo può anche portare a un’eccessiva quantità di commit.
Qui entra in gioco l’importanza di rimpiazzare i commit. Rimpiazzare i commit è il processo di combinare più voci di commit in un singolo commit coeso.
Ad esempio, immagina di lavorare su una funzione che implements un modulo di login, e crei i seguenti quattro commit:
Una volta completata la funzionalità, per il progetto complessivo, questi commit sono troppo dettagliati. Non abbiamo bisogno di sapere in futuro che abbiamo riscontrato un bug risolto durante lo sviluppo. Per garantire una cronologia pulita nel ramo principale, uniamo questi commit in un singolo commit:
Come Unire i Commit in Git: Rebase Interattivo
<diy5 Il metodo più comune per unire i commit è utilizzare un rebase interattivo. Iniziamo utilizzandolo con il comando:
git rebase -i HEAD~<number_of_commits>
Sostituite <number_of_commits>
con il numero di commit che vogliamo unire.
Nel nostro caso, abbiamo quattro commit, quindi il comando è:
git rebase -i HEAD~4
L’esecuzione di questo comando aprirà un editor di riga di comando interattivo:
La sezione superiore visualizza i commit, mentre la parte inferiore contiene commenti su come unire i commit.
Vediamo quattro commit. Per ognuno, dobbiamo decidere quale comando eseguire. Ci interessano i comandi pick
(p
) e squash
(s
). Per unire questi quattro commit in un singolo commit, possiamo scegliere il primo e unire i restanti tre.
Applichiamo i comandi modificando il testo che precede ogni commit, cambiando specificamente pick
in s
o squash
per il secondo, terzo e quarto commit. Per fare queste modifiche, dobbiamo entrare in modalità “INSERT” nel editor di testo a riga di comando premendo il tasto i
sulla tastiera:
Dopo aver premuto i
, apparirà il testo -- INSERT --
in fondo, indicando che siamo entrati in modalità insert. Ora, possiamo muovere il cursore con le frecce, cancellare caratteri e digitare come faremmo in un editor di testo standard:
Una volta soddisfatti delle modifiche, dobbiamo uscire dalla modalità di inserimento premendo il tasto Esc
sulla tastiera. Il passo successivo è salvare le nostre modifiche ed uscire dall’editor. Per fare questo, prima premiamo il tasto :
per indicare all’editor che intendiamo eseguire un comando:
In fondo all’editor, ora vediamo un punto e virgola :
che ci invita a inserire un comando. Per salvare le modifiche, usiamo il comando w
, che significa “write” (scrivere). Per chiudere l’editor, usiamo q
, che significa “quit” (uscire). Questi comandi possono essere combinati e scritti insieme wq
:
Per eseguire il comando, premiamo il tasto Enter
. Questa azione chiuderà l’editor corrente e ne aprirà uno nuovo, permettendoci di inserire il messaggio di commit per il commit appena fuso. L’editor visualizzerà un messaggio predefinito che comprende i messaggi dei quattro commit che stiamo fusing:
Consiglio di modificare il messaggio per riflettere accuratamente le modifiche implementate da questi commit combinati—dopotutto, lo scopo dello squashing è mantenere una cronologia pulita e facilmente leggibile.
Per interagire con l’editor e modificare il messaggio, premiamo di nuovo i
per entrare in modalità di modifica e modificare il messaggio a nostro piacimento.
In questo caso, sostituiamo il messaggio del commit con “Implementa modulo di login”. Per uscire dalla modalità di modifica, premiamo Esc
. Poi salviamo le modifiche premendo :
, inserendo il comando wq
e premendo Enter
.
Come Visualizzare la Cronologia dei Commit
Generalmente, richiamare l’intera cronologia dei commit può essere complicato. Per visualizzare la cronologia dei commit, possiamo usare il comando git log
. Nell’esempio menzionato, prima di eseguire lo squashing, eseguire il comando git log
avrebbe visualizzato:
Per navigare l’elenco dei commit, usa i tasti freccia su e giù. Per uscire, premi q
.
Possono usare git log
per confermare il successo dello squash. Eseguirlo dopo lo squash visualizzerà un singolo commit con il nuovo messaggio:
Pushing squashed commit
git push --force origin feature/login-form
Il push forzato sovrascriverà la cronologia dei commit sul ramo remoto e potrebbe interrompere chi sta lavorando su quel ramo. È buona norma comunicare con il team prima di fare questo
Un modo più sicuro per eseguire un push forzato, che riduce il rischio di interrompere i collaboratori, è utilizzare invece l’opzione --force-with-lease
:
git push --force-with-lease origin feature/login-form
Questa opzione ci assicura che eseguiamo il push forzato solo se il ramo remoto non è stato aggiornato dal nostro ultimo fetch o pull.
Squashing di commit specifici
Immaginiamo di avere cinque commit:
Supponiamo che vogliamo conservare i commit 1, 2 e 5 e unire i commit 3 e 4.
Quando si utilizza il rebase interattivo, i commit marcati per l’unione saranno combinati con il commit direttamente precedente. In questo caso, significa che vogliamo unire Commit4
in modo che si fonde in Commit3
.
Per fare questo, dobbiamo avviare una rebase interattiva che includa questi due commit. In questo caso, tre commit sono sufficienti, quindi utilizziamo il comando:
git rebase -i HEAD~3
poi, impostiamo Commit4
su s
perché venga unito a Commit3
:
Dopo aver eseguito questo comando ed elencare i commit, notiamo che i commit 3 e 4 sono stati uniti mentre gli altri rimangono invariati.
Squash da un commit specifico
Nel comando git rebase -i HEAD~3
, la parte HEAD
è un modo abbreviato per indicare l’ultimo commit. La sintassi ~3
viene utilizzata per specificare un antenato di un commit. Ad esempio, HEAD~1
si riferisce al genitore del commit HEAD
.
Nel rebase interattivo, i commit considerati includono tutti i commit antenati che portano al commit specificato nel comando. Nota che il commit specificato non è incluso:
Invece di usare HEAD, possiamo specificare direttamente l’hash di un commit. Per esempio, Commit2
ha un hash di dbf3cc118d6d7c08ef9c4a326b26dbb1e3fe9ddf
, quindi il comando:
git rebase -i dbf3cc118d6d7c08ef9c4a326b26dbb1e3fe9ddf
inizierebbe un rebase considerando tutti i commit effettuati dopo Commit2
. Pertanto, se vogliamo iniziare un rebase in un commit specifico e includere quel commit, possiamo usare il comando:
git rebase -i <commit-hash>~1
Risolvere i Conflitti Quando Si Uniscono Commits
Quando uniamo i commit, combiniamo più modifiche di commit in un singolo commit, il che può portare a conflitti se le modifiche si sovrappongono o divergono significativamente. Ecco alcuni scenari comuni in cui possono sorgere conflitti:
- Modifiche sovrapposte: Se due o più commit da unire hanno modificato le stesse righe di un file o righe strettamente correlate, Git potrebbe non essere in grado di riconciliare automaticamente questi cambiamenti.
- Stato di cambiamenti differenti: Se un commit aggiunge una certa porzione di codice e un altro commit modifica o elimina quella stessa porzione di codice, unire questi commit può portare a conflitti che devono essere risolti.
- Rinominare e modificare: Se un commit rinomina un file e i commit successivi apportano modifiche al vecchio nome, unire questi commit può confondere Git, causando un conflitto.
- Modifiche ai file binari: I file binari non si uniscono bene utilizzando strumenti di diff basati su testo. Se più commit modificano lo stesso file binario e proviamo a unirli, può verificarsi un conflitto perché Git non può automaticamente riconciliare questi cambiamenti.
- Storia complessa: Se i commit hanno una storia complessa con più fusioni, diramazioni o riportazioni nel mezzo, unireli può portare a conflitti a causa della natura non lineare dei cambiamenti.
Quando si unisce, Git cercherà di applicare ogni cambiamento uno per uno. Se incontra conflitti durante il processo, si fermerà e ci permetterà di risolverli.
Il conflitto sarà contrassegnato con i marker di conflitto <<<<<<
e >>>>>>
. Per gestire i conflitti, dobbiamo aprire i file e risolverli manualmente selezionando quale parte del codice vogliamo mantenere.
Dopo aver risolto i conflitti, dobbiamo aggiungere i file risolti utilizzando il comando git add
. Poi possiamo continuare il rebase utilizzando il seguente comando:
git rebase --continue
Per ulteriori informazioni sui conflitti in Git, consulta questo tutorial su come risolvere i conflitti di fusione in Git.
Alternative alla S squashing con il Rebase
Il comando git merge --squash
è un metodo alternativo a git rebase -i
per combinare più commit in un singolo commit. Questo comando è particolarmente utile quando vogliamo unire le modifiche da un ramo al ramo principale, schiacciando tutti i singoli commit in uno. Ecco una panoramica su come schiacciare utilizzando git merge
:
- Navigiamo al ramo di destinazione nel quale vogliamo incorporare le modifiche.
- Eseguiamo il comando
git merge --squash <nome-ramo>
sostituendo<nome-ramo
con il nome del ramo. - Effettuiamo il commit delle modifiche con
git commit
per creare un unico commit che rappresenta tutte le modifiche dal ramo delle funzionalità.
Per esempio, diciamo che vogliamo integrare le modifiche del ramo feature/login-form
nel main
come un singolo commit:
git checkout main git merge --squash feature-branch git commit -m "Implement login form"
Questi sono i limiti di questo approccio rispetto a git rebase -i
:
- Granularità di controllo: Meno controllo sui singoli commit. Con il rebase possiamo scegliere quali commit unire, mentre il merge costringe a combinare tutte le modifiche in un singolo commit.
- Storia intermedia:Quando si utilizza il merge, la cronologia dei singoli commit della branch di sviluppo viene persa nella branch principale. Questo può rendere più difficile tracciare le modifiche incrementali apportate durante lo sviluppo della funzionalità.
- Revisione pre-commit:Since it stages all changes as a single change set, we cannot review or test each commit individually before squashing, unlike during an interactive rebase where each commit can be reviewed and tested in sequence.
Conclusione
Incorporare frequenti e piccoli commit nel flusso di lavoro di sviluppo promuove la collaborazione e una documentazione chiara, ma può anche ingombrare la cronologia del progetto. Squashare i commit bilancia, preservando i milestones importanti mentre elimina il rumore delle modifiche iterative minori.
Per imparare di più su Git, ti consiglio queste risorse:
Source:
https://www.datacamp.com/tutorial/git-squash-commits