Close

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.

Una cronologia di commit con fork

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.

Database
materiale correlato

Come spostare un repository Git completo

Logo di Bitbucket
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:

Merging main into feature branch

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.

Rebasing feature branch into main

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:

Esecuzione dello squash di un commit con una riassegnazione interattiva

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:

Rebasing the main branch

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:

Sviluppo di una funzione in un branch dedicato

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.

Riassegnazione su Head~3

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:

Collaborazione sullo stesso branch di funzioni

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.

Confronto tra merge e riassegnazione su un branch remoto

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.

Integrating a feature into main with and without a rebase

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.

Le persone collaborano utilizzando una parete piena di strumenti

Blog di Bitbucket

Illustrazione su Devops

Percorso di apprendimento DevOps

Funzione Demo Den per demo con esperti Atlassian

Come Bitbucket Cloud funziona con Atlassian Open DevOps

Iscriviti alla nostra newsletter DevOps

Thank you for signing up