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.
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.
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.
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 commitr,
reword`: usa la commit, ma edita il messaggio di commite,
edit`: usa la commiit, ma si ferma in attesa di ulteriori modifiches
,squash
: usa la commit, ma la accorpa con quella precedentef
,fixup
: come “squash”, ma scarta il messaggio di commit in favore di quello precedentex
,exec
: esegue una comando usando la shelld
,drop
: rimuove la commit (contrario dipick
)
Rebase avanzato
Supponiamo di avere una situazione simile alla seguente:
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
.
Successivamente potremo spostarci su master
ed effettuare un fast-forward con git merge
1$ git checkout master
2$ git merge 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
Infine, fare il fast-forward:
1$ git checkout master
2$ git merge server
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 commits
= elimina il messaggio della commit, accorpa le stesse in un’unica commit e ci farà inserire il messaggiof
= 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:
- 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.
- 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!