Full Stack

Il comando git rebase in Git

Autore

Manuel Ricci

In git ci sono due modi per integrare le branch: merge (visto nella lezione precedente) e rebase. merge è il più semplice. In una cronologia diverta performa un merge a tre vie tra gli snapshot delle due branch e la commit genitore comune e crea una nuova commit chiamata merge commit.

repository dopo merge

Dall’altra parte, rebase, prende la parte modificata, nell’esempio introdotta da C4, e la applica a C3. Questo è il rebasing. Prendere tutte le modifiche salvate in una branch e applicarle in una branch differente.

repository dopo rebase

Rispetto al merge a tre vie, il rebasing prende l’antenato comune delle due branch, controlla i cambiamenti introdotti con ogni commit nella branch dove ci troviamo, salva le differenze in file temporanei, resetta la branch corrente alla stessa commit nella quale si trova quella sulla quale vogliamo applicare il rebasing e infine applica le modifiche.

Il merging vero e proprio però non è ancora avvenuto, per poter unificare a tutti gli effetti la branch bisognerà fare un fast-forward con merge.

Se alla fine ci troviamo a fare un merge, a cosa serve il rebase? Per quanto l’esito di tutta questa operazione sia il medesimo di un normalissimo merging, la vera differenza la si può notare nella cronologia. Con rebasing si tende ad avere una cronologia più pulita.

Se si dovesse osservare il log in seguito ad un rebasing e il conseguente merging, le modifiche sembreranno avvenute una in seguito all’altra, quando invece sappiamo che sono avvenute in parallelo.

fast-forward dopo rebase

Rebase standard e rebase interattivo

Attraverso l’uso della flag -i o --interactive è possibile attivare la modalità interattiva di rebase, la quale aprirà il nostro editor predefinito e per ogni commit potremo decidere cosa fare.

1$ git rebase main -i

Oppure

1$ git rebase -i HEAD~10

La differenza tra i due comandi è che il primo può essere eseguito sulla branch che vogliamo successivamente unificare, mentre il secondo può essere usato per riscrivere la storia della branch dove ci troviamo (dalla HEAD fino a 10 commit passate).

I comandi del rebase interattivo

Il rebase interattivo offre diversi comandi che possono essere utilizzati per decidere il da farsi con ogni singola commit, i più frequenti sono:

  • p, pick: usa la commit
  • r,reword`: usa la commit, ma edita il messaggio di commit
  • e,edit`: usa la commiit, ma si ferma in attesa di ulteriori modifiche
  • s, squash: usa la commit, ma la accorpa con quella precedente
  • f, fixup: come “squash”, ma scarta il messaggio di commit in favore di quello precedente
  • x, exec: esegue una comando usando la shell
  • d, drop: rimuove la commit (contrario di pick)

Rebase avanzato

Supponiamo di avere una situazione simile alla seguente:

rebase avanzato con più branch

La branch server contiene delle funzionalità server-side, mentre la branch client contiene delle funzionalità client-side.

Dopo le varie modifiche, la branch client è pronta per la release, ma per la branch server preferite fare ulteriori test prima di procedere al rilascio.

Possiamo fare il rebase per fare in modo che le modifiche di client vengano riprodotte su master in questo modo:

1$ git rebase --onto server client

Tradotto: prendi la branch client, individua le modifiche fatte da quando ha diverto dalla branch server e riproducile su client come se fosse stata diverta da master.

esito del primo rebase sulla branch client

Successivamente potremo spostarci su master ed effettuare un fast-forward con git merge

1$ git checkout master
2$ git merge client

merge di master con client

Infine, dato che le funzionalità server side sono state testate e pronte per la produzione vogliamo fare la stessa cosa con la branch server, procediamo quindi con il rebase:

1$ git rebase master server

rebase di server con main

Infine, fare il fast-forward:

1$ git checkout master
2$ git merge server

merge e cancellazione delle branch con master

E non dimenticatevi di cancellare le due branch

1$ git branch -d client
2$ git branch -d server

Gli svantaggi di usare rebase

Tutto questo chiaramente ha qualche svantaggio, possono essere fatti dei danni, ma fortunatamente non sono irreparabili, ma è sempre meglio evitare di fare casino, soprattutto se si lavora in team.

Uno dei principali rischi è la proliferazione di conflitti su branch che sono state separate da main o master per tanto tempo.

Nel momento del rebase se sono presenti numerose commit, queste potrebbero essere in conflitto con quanto nella branch che sulla quale si vuole performare il rebase.

Problema che comunque non ci si pone se facciamo rebase e commit più frequenti.

Più serio invece è il problema legato alla perdita di commit dal rebasing interattivo della cronologia. squash e drop rimuovo completamente le commit dalla cronologia.

Possono essere comunque ripristinati, annullando il rebase, con l’auto del comando reflog (comando che vedremo più avanti). rebase di per sé non è pericoloso, anzi è molto utile. I problemi nascono se si esegue la riscrittura della cronologia e si forza il push ad una branch remota condivisa con altri utenti.

Questa la possiamo considerare a tutti gli effetti una worst practice, dato che c’è un elevato rischio di sovrascrivere il lavoro di coloro che eseguono un pull.

Anche quest’ultima situazione è comunque ripristinabile attraverso reflog, ma rimane comunque una rottura, quindi fate sempre attenzione.

Altri usi di rebase

In modalità interattiva possiamo usare e o edit per permetterci di fare ulteriori modifiche da integrare in una commit.

Altrimenti con le flag r, s o f possiamo riscrivere i messaggi:

  • r = riscrittura del singolo messaggio di commit
  • s = elimina il messaggio della commit, accorpa le stesse in un’unica commit e ci farà inserire il messaggio
  • f = scarta il messaggio in favore di quello della commit precedente

Rebase penso valga la pena inserirlo nella propria lista dei comandi di tutti i giorni soprattutto per lo squash. La capacità di accorpare le commit ci permette di mantenere una storia pulita. Provate ad immaginare, siete convinti che la modifica fatta risolva un bug, in sviluppo tutto ok, i test sono ok, ma in produzione qualcosa va storto e fate vari tentativi di fix, accumulate 3 o 4 messaggi di commit che solo a vederli vi fanno venire il nervoso. Quando finalmente anche la produzione decide di collaborare, rimangono però le commit fatte per testare varie soluzioni, le teniamo lì o li rimuoviamo? Personalmente preferisco fare un bello squash e riscrivere la cronologia per averla più in ordine, ma qui è solo una questione di punti di vista.

Meglio merge o rebase?

Ora che sai quali sono le potenzialità di rebase, dovresti aver capito che alla domande che fa da titolo a questo paragrafo, la risposta dovrebbe essere un bel dipende.

I punti di vista sulla quale ci si divide sono sostanzialmente due:

  1. La storia della repo riporta ciò che è successo effettivamente durante la lavorazione. Modificare la storia è al limite del blasfemo. Se ci dovessero essere una serie di merge al limite del ridicolo o messaggi di commit poco chiari? Poco importa, la cronologia non si tocca e deve essere preservata.
  2. La storia della repo è la storia di come il progetto è stato fatto. Quando si lavora sul progetto è giusto avere un rapporto su tutti gli sbagli, ma quando è il momento di mostrarlo al mondo, vorresti far vedere qualcosa di più coerente e meno caotico.

Quale dei due è il migliore, non sta a nessuno giudicare la superiorità di un comando rispetto ad un altro. Ogni team, ogni progetto è diverso.

Conoscendo sia merge che rebase potrete scegliere in autonomia il più indicato in base alla situazione.

Il consiglio è comunque quello di fare un rebase delle modifiche locali, prima di sincronizzarle con eventuali repo remote, così da mettere a posto quanto fatto, ma mai fare il rebase di qualcosa che è stato già sincronizzato.

Conclusioni

Anche con questa lezione ci siamo portati a casa un comando molto importante, nella prossima lezione vedremo una valanga di comandi e faremo un giretto su GitHub. Queste ultime lezioni saranno belle toste.

Caricamento...

Diventiamo amici di penna? ✒️

Iscriviti alla newsletter per ricevere una mail ogni paio di settimane con le ultime novità, gli ultimi approfondimenti e gli ultimi corsi gratuiti puubblicati. Ogni tanto potrei scrivere qualcosa di commerciale, ma solo se mi autorizzi, altrimenti non ti disturberò oltre.

Se non ti piace la newsletter ti ricordo la community su Discord, dove puoi chiedere aiuto, fare domande e condividere le tue esperienze (ma soprattutto scambiare due meme con me). Ti aspetto!

Ho in previsione di mandarti una newsletter ogni due settimane e una commerciale quando capita.