Merging vs. rebasing
Il comando git rebase
è noto per essere il mago di Git da cui i principianti devono stare lontani, ma in realtà può rendere la vita molto più facile ai team di sviluppo, se usato con attenzione. In questo articolo, confronteremo git rebase
con il relativo comando git merge
e identificheremo tutte le potenziali opportunità per incorporare la riassegnazione in un tipico flusso di lavoro di Git.
Conceptual overview
La prima cosa da comprendere su git rebase
è che risolve lo stesso problema di git merge
. Entrambi questi comandi sono progettati per integrare le modifiche da un branch a un altro: l'unica differenza è il modo in cui lo fanno.
Pensa a quello che succede quando inizi a lavorare su una nuova funzione in un branch dedicato e un altro membro del team aggiorna il branch main
con nuovi commit. Ciò causa il fork della cronologia, concetto che dovrebbe essere familiare a chiunque abbia utilizzato Git come strumento di collaborazione.
Ora, supponiamo che i nuovi commit in main
siano pertinenti alla funzione su cui stai lavorando. Per incorporarli nel tuo branch feature
, hai due opzioni: eseguire il merge o la riassegnazione.
materiale correlato
Come spostare un repository Git completo
Scopri la soluzione
Impara a utilizzare Git con Bitbucket Cloud
The merge option
L'opzione più semplice è quella di eseguire il merge del branch main
nel branch di funzioni tramite qualcosa di simile:
git checkout feature
git merge main
Oppure, puoi sintetizzare il tutto in una sola riga:
git merge feature main
Questa operazione crea un nuovo "commit di merge" nel branch feature
che unisce le cronologie di entrambi i branch, creando una struttura di branch simile a questa:
Uno degli aspetti positivi del merge è che si tratta di un'operazione non distruttiva. I branch esistenti non vengono modificati in alcun modo e vengono quindi evitate tutte le potenziali insidie della riassegnazione (descritte di seguito).
D'altra parte, ciò significa anche che il branch feature
avrà un commit di merge non pertinente ogni volta che è necessario incorporare le modifiche upstream. Se main
è molto attivo, può inquinare un po' la cronologia del branch di funzioni. Sebbene sia possibile mitigare questo problema con le opzioni avanzate di git log
, ciò può rendere difficile agli altri sviluppatori comprendere la cronologia del progetto.
The rebase option
In alternativa al merge, è possibile riassegnare il branch feature
sul branch main
utilizzando i seguenti comandi:
git checkout feature
git rebase main
Questa operazione sposta l'intero branch feature
per fare in modo che inizi sulla punta del branch main
, incorporando efficacemente tutti i nuovi commit in main
. Ma, invece di usare un commit di merge, la riassegnazione riscrive la cronologia del progetto creando nuovi commit per ogni commit nel branch originale.
Il principale vantaggio offerto dalla riassegnazione è una cronologia di progetto molto più ordinata. Innanzitutto, questa operazione elimina i commit di merge non necessari richiesti da git merge
. In secondo luogo, come puoi vedere nel diagramma sopra, la riassegnazione si traduce inoltre in una cronologia di progetto perfettamente lineare: puoi seguire la punta del branch feature
fino all'inizio del progetto senza alcun fork. In questo modo, è più facile spostarsi all'interno del progetto con comandi come git log
, git bisect
e gitk
.
Ma per ottenere questa cronologia dei commit così pulita, occorre rispettare assolutamente due punti: sicurezza e tracciabilità. Se non segui la regola d'oro della riassegnazione, riscrivere la cronologia del progetto può essere potenzialmente catastrofico per il flusso di lavoro di collaborazione. E, cosa meno importante, con la riassegnazione si perde il contesto fornito dal commit di merge: non è possibile vedere quando le modifiche upstream sono state incorporate nella funzione.
Interactive rebasing
La riassegnazione interattiva ti dà l'opportunità di modificare i commit man mano che vengono spostati nel nuovo branch. Questa operazione è ancora più efficace della riassegnazione automatica, poiché offre il controllo completo sulla cronologia di commit del branch. In genere, questo tipo di riassegnazione viene utilizzato per riordinare una cronologia disordinata prima di eseguire il merge di un branch di funzioni in main
.
Per iniziare una sessione di riassegnazione interattiva, invia l'opzione i
al comando git rebase
:
git checkout feature
git rebase -i main
Si aprirà un editor di testo che elenca tutti i commit che stanno per essere spostati:
pick 33d5b7a Message for commit #1
pick 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3
Questo elenco definisce esattamente l'aspetto del branch dopo l'esecuzione della riassegnazione. Modificando il comando pick
e/o riordinando le voci, puoi ordinare la cronologia del branch come meglio preferisci. Ad esempio, se il secondo commit risolve un piccolo problema del primo commit, puoi unirli in un unico commit con il comando fixup
:
pick 33d5b7a Message for commit #1
fixup 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3
Quando salvi e chiudi il file, Git eseguirà la riassegnazione secondo le tue istruzioni, creando una cronologia di progetto simile alla seguente:
Eliminare commit insignificanti come questo semplifica notevolmente la comprensione della cronologia della funzione. Si tratta di un'operazione che git merge
non è semplicemente in grado di fare.
The golden rule of rebasing
Una volta capito cos'è la riassegnazione, la cosa più importante da imparare è quando non eseguirla. La regola d'oro di git rebase
dice di non usare mai questo comando nei branch pubblici.
Ad esempio, pensa a cosa succederebbe se eseguissi la riassegnazione di main
sul branch feature
:
La riassegnazione sposta tutti i commit in main
nella punta di feature
. Il problema è che questa operazione è avvenuta solo nel tuo repository. Tutti gli altri sviluppatori stanno ancora lavorando sul branch main
originale. Dal momento che la riassegnazione si traduce in nuovi commit, Git penserà che la cronologia del tuo branch main
sia diversa da quella di tutti gli altri.
L'unico modo per sincronizzare i due branch main
è eseguirne nuovamente il merge, creando un ulteriore commit di merge e due set di commit che contengono le stesse modifiche (quelle originali e quelle del branch riassegnato). Inutile dire che questa è una situazione molto confusionaria.
Quindi, prima di eseguire git rebase
, chiediti sempre se qualcun altro sta lavorando sul branch in questione. Se la risposta è sì, allontanati dalla tastiera e inizia a pensare a un modo non distruttivo per apportare le modifiche (ad esempio, tramite il comando git revert
). Se invece nessun altro sta lavorando sul branch in questione, puoi pure riscrivere la cronologia come desideri.
Force-pushing
Se provi a eseguire il push del branch main
riassegnato su un repository remoto, Git ti impedirà di farlo perché si genererà un conflitto con il branch main
remoto. Tuttavia, puoi forzare il push inviando il flag --force
, in questo modo:
# Be very careful with this command! git push --force
Questo comando sovrascrive il branch main
remoto per fare in modo che corrisponda a quello riassegnato del tuo repository e crea molta confusione per il resto del team. Quindi, fai molta attenzione e usa questo comando solo se sai esattamente quello che stai facendo.
Una delle poche volte in cui dovresti forzare il push è quando hai eseguito una pulizia locale dopo aver eseguito il push di un branch di funzioni privato in un repository remoto (ad esempio, per scopi di backup). È come se dicessi agli altri membri del team che hai sbagliato a eseguire il push della versione originale del branch di funzioni e li stessi quindi informando di usare la versione corrente. Ripetiamo: è importante che nessun altro stia lavorando sui commit della versione originale del branch di funzioni.
Workflow walkthrough
Il team può scegliere in che misura incorporare la riassegnazione nel flusso di lavoro di Git esistente. In questa sezione, esamineremo i vantaggi che la riassegnazione può offrire nelle varie fasi dello sviluppo di una funzione.
La prima fase di qualsiasi flusso di lavoro che utilizza git rebase
consiste nella creazione di un branch dedicato per ogni funzione. Ciò consente di avere la struttura di branch necessaria per utilizzare in sicurezza la riassegnazione:
Local cleanup
Uno dei modi migliori per incorporare la riassegnazione nel flusso di lavoro consiste nell'effettuare la pulizia delle funzioni locali in corso. Eseguendo periodicamente una riassegnazione interattiva, puoi assicurarti che ogni commit nella funzione sia mirato e pertinente. In questo modo, puoi dedicarti alla scrittura del codice senza preoccuparti di suddividerlo in commit isolati: puoi risistemarlo al termine del processo.
Quando richiami git rebase
, hai due opzioni per la nuova base: il branch di funzioni principale (ad esempio main
) o un commit precedente nella funzione. Nella sezione Riassegnazione interattiva, abbiamo visto un esempio della prima opzione. L'ultima opzione è interessante quando devi solo correggere gli ultimi commit. Ad esempio, il comando seguente avvia una riassegnazione interattiva solo degli ultimi 3 commit.
git checkout feature git rebase -i HEAD~3
Specificando HEAD~3
come nuova base, in realtà non stai spostando il branch, ma stai semplicemente riscrivendo in modo interattivo i 3 commit che lo seguono. Tieni presente che questa operazione non incorporerà le modifiche upstream nel branch feature
.
Se vuoi riscrivere l'intera funzione usando questo metodo, il comando git merge-base
può essere utile per trovare la base originale del branch feature
. Quanto segue restituisce l'ID di commit della base originale, che puoi quindi inviare a git rebase
:
git merge-base feature main
Questo uso della riassegnazione interattiva è un ottimo modo per introdurre git rebase
nel flusso di lavoro, poiché riguarda solo i branch locali. L'unica cosa che gli altri sviluppatori vedranno è il prodotto finito, che dovrebbe essere una cronologia del branch di funzioni pulita e facile da seguire.
Ma ancora una volta, questa procedura funziona solo per i branch di funzioni privati. Se stai collaborando con altri sviluppatori tramite lo stesso branch di funzioni, tale branch è pubblico e non ti è permesso riscrivere la sua cronologia.
Non esiste un'alternativa a git merge
per eseguire la pulizia dei commit locali con una riassegnazione interattiva.
Incorporating upstream changes into a feature
Nella sezione Panoramica concettuale, abbiamo visto come un branch di funzioni possa incorporare le modifiche upstream da main
usando git merge
o git rebase
. Il merge è un'opzione sicura che conserva l'intera cronologia del repository, mentre la riassegnazione crea una cronologia lineare spostando il branch di funzioni sulla punta del branch main
.
Questo uso di git rebase
è simile a una pulizia locale (e può essere eseguito contemporaneamente), ma nel processo incorpora quei commit upstream provenienti da main
.
Tieni presente che è assolutamente consentito eseguire la riassegnazione su un branch remoto anziché su main
. Ad esempio, quando si collabora alla stessa funzione con un altro sviluppatore ed è necessario incorporare le modifiche nel repository.
Ad esempio, se tu e John, un altro sviluppatore, avete aggiunto dei commit al branch feature
, il repository potrebbe essere simile a quanto riportato di seguito dopo che avrai recuperato il branch feature
remoto dal repository di John:
Puoi risolvere questo fork esattamente nello stesso modo in cui integri le modifiche upstream da main
: eseguendo il merge del branch feature
locale con john/feature
oppure riassegnando il branch feature
locale sulla punta di john/feature
.
Nota che questa riassegnazione non viola la regola d'oro della riassegnazione perché vengono spostati solo i commit di feature
locali; tutto ciò che è avvenuto prima non viene toccato. È come dare il comando di aggiungere le modifiche al lavoro già effettuato da John. Nella maggior parte dei casi, si tratta di una procedura più intuitiva rispetto alla sincronizzazione con il branch remoto tramite un commit di merge.
Per impostazione predefinita, il comando git pull
esegue un merge, ma puoi forzarlo per integrare il branch remoto con una riassegnazione inviando l'opzione --rebase
.
Reviewing a feature with a pull request
Se usi le pull request come parte del processo di revisione del codice, evita di usare git rebase
dopo aver creato la pull request. Non appena esegui la pull request, gli altri sviluppatori esamineranno i tuoi commit, il che significa che si tratta di un branch pubblico. Riscrivere la sua cronologia renderà impossibile a Git e agli altri membri del team tenere traccia di eventuali commit di follow-up aggiunti alla funzione.
Le eventuali modifiche apportate dagli sviluppatori devono essere incorporate con git merge
invece che con git rebase
.
Per questo motivo, di solito è una buona idea eseguire la pulizia del codice con una riassegnazione interattiva prima di inviare la pull request.
Integrating an approved feature
Dopo che una funzione è stata approvata dal team, hai la possibilità di riassegnare la funzione sulla punta del branch main
prima di utilizzare git merge
per integrare la funzione nella base di codice principale.
Questa è una situazione simile all'incorporazione delle modifiche upstream in un branch di funzioni, ma, dal momento che non ti è permesso riscrivere i commit nel branch main
, devi usare git merge
per integrare la funzione. Tuttavia, eseguendo una riassegnazione prima del merge, hai la certezza che il merge sarà con avanzamento rapido e che risulterà quindi in una cronologia perfettamente lineare. Avrai inoltre la possibilità di eseguire lo squash degli eventuali commit di follow-up aggiunti durante una pull request.
Se non sei del tutto a tuo agio con git rebase
, puoi sempre eseguire la riassegnazione in un branch temporaneo. In questo modo, se introduci accidentalmente degli errori nella cronologia della funzione, puoi estrarre il branch originale e riprovare. Ad esempio:
git checkout feature
git checkout -b temporary-branch
git rebase -i main
# [Clean up the history]
git checkout main
git merge temporary-branch
Riepilogo
Questo è tutto ciò che devi sapere per iniziare ad eseguire la riassegnazione dei branch. Se preferisci una cronologia pulita e lineare priva di commit di merge non necessari, utilizza git rebase
invece di git merge
quando integri le modifiche da un altro branch.
D'altra parte, se vuoi preservare la cronologia completa del progetto ed evitare il rischio di riscrivere i commit pubblici, puoi continuare a usare git merge
. Entrambe le opzioni sono perfettamente valide, ma almeno ora puoi scegliere se sfruttare i vantaggi di git rebase
.
Condividi l'articolo
Argomento successivo
Letture consigliate
Aggiungi ai preferiti queste risorse per ricevere informazioni sui tipi di team DevOps e aggiornamenti continui su DevOps in Atlassian.