← Torna al blog

Rebase interattivo senza paura: una guida pratica

tutorial git

Lo strumento più evitato di Git

Chiedi a una stanza piena di sviluppatori quale comando Git li rende nervosi, e git rebase -i finirà ai primi posti. Il rebase interattivo ha una certa reputazione: riscrive la cronologia, apre una strana todo list nel tuo editor e sembra pericolosamente facile rompere qualcosa. Così la maggior parte delle persone semplicemente lo evita -- e le loro pull request arrivano cariche di commit come "wip", "fix typo" e "actually fix it".

È un peccato, perché il rebase interattivo è il miglior strumento in assoluto per trasformare un branch disordinato in una cronologia pulita e facile da revisionare. In questa guida lo demistificheremo con un esempio concreto che ci accompagnerà fino alla fine: un branch di feature con 9 commit disordinati che rimodelleremo in 3 commit puliti prima di aprire una pull request. Se ti stai ancora chiedendo se il rebase sia davvero la scelta giusta per il tuo flusso di lavoro, inizia dal nostro confronto tra rebase e merge -- questo articolo presuppone che tu abbia deciso di fare rebase e voglia farlo bene.

Perché una cronologia pulita è importante

Ripulire i commit prima di una pull request non è una questione di estetica. Una cronologia ordinata ripaga in tre modi molto pratici:

  • Revisionabilità. Un revisore che legge tre commit logici -- "Add login form with validation", "Add remember me option", "Add error messages" -- può revisionare ogni modifica in isolamento. Lo stesso codice sparso su nove commit a metà lo costringe a revisionare l'intero diff in una volta sola, e la qualità della revisione crolla.
  • git bisect. Quando un bug compare settimane dopo, git bisect percorre la cronologia per trovare il commit che l'ha introdotto. Questo funziona solo se ogni commit compila e ha senso da solo. Una cronologia piena di commit "wip" che non compilano nemmeno rende bisect quasi inutile.
  • Revert. Se una funzionalità deve essere rimossa, fare il revert di un commit autonomo è banale. Fare il revert di una funzionalità spalmata su nove commit intrecciati è un pomeriggio di archeologia.

Il nostro esempio guida: 9 commit disordinati

Ecco il branch che ripuliremo. È una cronologia di lavoro perfettamente normale -- è così che tutti scrivono codice nella realtà:

$ git log --oneline main..feature/login
c9d0e1f oops forgot the css file
b8c9d0e add error messages
a7b8c9d wip 2
f6a7b8c add remember me checkbox
e5f6a7b actually fix it
d4e5f6a fix typo
c3d4e5f add form validation
b2c3d4e wip
a1b2c3d add login form skeleton

Non c'è niente di sbagliato nel committare così mentre lavori. Il problema è solo se finisce in produzione così. Il nostro obiettivo è arrivare a tre commit:

  1. Add login form with validation
  2. Add remember me option
  3. Add error messages with styling

Avviare il rebase: git rebase -i HEAD~9

Per riscrivere gli ultimi 9 commit, esegui:

git rebase -i HEAD~9

HEAD~9 significa "il commit 9 passi prima di quello attuale" -- tutto ciò che viene dopo quel punto è modificabile. Git apre il tuo editor con la todo list:

pick a1b2c3d add login form skeleton
pick b2c3d4e wip
pick c3d4e5f add form validation
pick d4e5f6a fix typo
pick e5f6a7b actually fix it
pick f6a7b8c add remember me checkbox
pick a7b8c9d wip 2
pick b8c9d0e add error messages
pick c9d0e1f oops forgot the css file

Due cose da capire prima di toccare qualsiasi cosa. Primo, questo è solo un file di testo: non succede nulla finché non salvi e chiudi, e se elimini tutte le righe o chiudi senza salvare modifiche significative, il rebase viene annullato senza alcun danno. Secondo, l'ordine è dal più vecchio al più recente -- l'opposto di git log. Git rigiocherà i commit dall'alto verso il basso, applicando il verbo all'inizio di ogni riga.

I sei verbi

pick

Mantieni il commit esattamente com'è. È il valore predefinito per ogni riga.

reword

Mantieni le modifiche del commit, ma fermati per permetterti di modificare il messaggio. Perfetto per sistemare "wip 2" senza toccare il codice.

edit

Metti in pausa il rebase su questo commit. Puoi fare amend, dividerlo in più commit o testarlo, poi continuare con git rebase --continue. È il verbo più avanzato e quello che userai meno spesso.

squash

Fondi questo commit con quello sopra di esso, e apri l'editor per combinare i due messaggi di commit in uno solo.

fixup

Come squash, ma scarta completamente il messaggio di questo commit. Il commit sopra mantiene il suo messaggio invariato. È ciò che vuoi per i commit "fix typo" -- i loro messaggi non contengono alcuna informazione che valga la pena conservare.

drop

Elimina il commit e le sue modifiche completamente. Cancellare la riga dalla todo list ha lo stesso effetto, ma scrivere drop rende esplicita la tua intenzione.

Ripulire il branch, passo dopo passo

Ora applichiamo tutto questo ai nostri 9 commit. Servono due operazioni: riordinare e cambiare i verbi.

Prima, il riordino. Guardando i diff, "oops forgot the css file" è in realtà il foglio di stile per la checkbox remember me, non per i messaggi di errore. Nella todo list, riordinare significa semplicemente spostare una riga: taglia la riga c9d0e1f e incollala subito dopo il gruppo remember me. Git rigiocherà i commit nel nuovo ordine.

Poi, i verbi. Tutto fino a "actually fix it" appartiene al form di login, quindi collassa nel primo commit. Usiamo squash su "add form validation" perché vogliamo fondere il suo messaggio in quello finale, e fixup sui commit di rumore i cui messaggi meritano di sparire. Ecco la todo list modificata:

pick a1b2c3d add login form skeleton
fixup b2c3d4e wip
squash c3d4e5f add form validation
fixup d4e5f6a fix typo
fixup e5f6a7b actually fix it
pick f6a7b8c add remember me checkbox
fixup a7b8c9d wip 2
fixup c9d0e1f oops forgot the css file
reword b8c9d0e add error messages

Salva e chiudi. Git rigioca i commit e si ferma due volte: una per lasciarti scrivere il messaggio combinato del primo gruppo (scrivi "Add login form with validation"), e una per il reword (scrivi "Add error messages with styling"). Per il commit remember me, un rapido reword avrebbe funzionato altrettanto bene -- qui l'abbiamo lasciato come pick e il suo messaggio originale era già decente. Il risultato:

$ git log --oneline main..feature/login
3f4e5d6 Add error messages with styling
2e3d4c5 Add remember me option
1d2c3b4 Add login form with validation

Nove commit sono diventati tre, ognuno autonomo e revisionabile. Nota che tutti gli hash sono cambiati -- questi sono commit completamente nuovi. Tieni a mente questo dettaglio; è il cuore della regola d'oro qui sotto.

La mossa da pro: --autosquash

Una volta che ti senti a tuo agio, puoi preparare la pulizia mentre lavori invece di sistemare tutto alla fine. Quando correggi qualcosa in un commit precedente, registralo così:

git commit --fixup a1b2c3d

Git crea un commit il cui messaggio è fixup! add login form skeleton. Più tardi, esegui:

git rebase -i --autosquash main

Git precompila la todo list con ogni commit fixup! già spostato sotto il suo commit di destinazione e marcato come fixup. Devi solo salvare e chiudere. Per rendere questo il comportamento predefinito, impostalo una volta sola:

git config --global rebase.autosquash true

La regola d'oro: mai fare rebase di commit condivisi

Il rebase non modifica i commit -- li sostituisce con commit nuovi. Se i tuoi colleghi hanno già fatto pull dei vecchi commit e ci hanno basato sopra del lavoro, riscrivere quei commit crea due cronologie divergenti, e il merge successivo diventa un pasticcio di duplicati e conflitti.

La regola è semplice: fai rebase solo di commit che non sono stati ancora pushati, o che vivono su un branch su cui lavori solo tu. Ripulire il proprio branch di feature prima di aprire una pull request è il caso sicuro da manuale. Se hai già pushato il branch e ne sei ancora l'unico autore, pusha la cronologia riscritta con git push --force-with-lease, che si rifiuta di sovrascrivere il lavoro che qualcun altro ha pushato nel frattempo. Non riscrivere mai main né alcun branch da cui il tuo team fa pull.

Quando le cose vanno storte

Durante un rebase possono succedere due cose. Se un commit rigiocato entra in conflitto con una modifica precedente, Git si ferma e ti chiede di risolvere il conflitto, poi di fare git add dei file ed eseguire git rebase --continue. E se in qualsiasi momento ti senti perso, c'è un pulsante di espulsione totale:

git rebase --abort

Questo riporta il branch esattamente allo stato in cui si trovava prima che iniziassi, senza fare domande. Finché ricordi che --abort esiste, un rebase in corso non potrà mai intrappolarti.

E se il rebase è terminato e ti accorgi che il risultato è sbagliato? Anche in quel caso, nulla è perso. I vecchi commit esistono ancora -- Git li conserva per settimane, e il reflog registra ogni posizione a cui il tuo branch abbia mai puntato, quindi puoi tornare allo stato pre-rebase con un singolo reset. Copriamo questa rete di sicurezza in dettaglio in recuperare commit persi con git reflog. E se il tuo errore è più piccolo -- solo l'ultimo commit deve essere cambiato -- non ti serve affatto un rebase: vedi come annullare un commit Git.

Il rebase interattivo in GitSquid

La todo list è potente, ma modificare i verbi in un file di testo è esattamente la parte che rende le persone nervose. GitSquid la sostituisce con un rebase interattivo visuale: riordini i commit con il drag & drop e scegli l'azione per ogni commit -- pick, squash, fixup, reword o drop -- da un menu a tendina. L'intero piano è visibile a colpo d'occhio prima che qualsiasi cosa venga eseguita, il che elimina gran parte del fattore paura.

GitSquid ti aiuta anche prima di iniziare: il Conflict Predictor, incluso nel piano gratuito, mostra quali file andranno in conflitto prima che tu lanci un rebase, così sai a cosa vai incontro invece di scoprire i conflitti a metà strada. Il rebase è disponibile direttamente dai menu contestuali di branch e commit sul grafo dei commit.

Riferimento rapido

Comando / verbo Cosa fa
git rebase -i HEAD~n Riscrive interattivamente gli ultimi n commit
pick Mantiene il commit così com'è
reword Mantiene le modifiche, modifica il messaggio
edit Pausa su questo commit per fare amend o dividerlo
squash Fonde nel commit precedente, combina i messaggi
fixup Fonde nel commit precedente, scarta questo messaggio
drop Elimina il commit completamente
git commit --fixup <hash> Registra una correzione destinata a un commit precedente
git rebase -i --autosquash Posiziona automaticamente i commit fixup! nella todo list
git rebase --continue Riprende dopo aver risolto un conflitto
git rebase --abort Annulla e ripristina lo stato pre-rebase

Il rebase interattivo ricompensa un po' di pratica con molta potenza. Inizia su un branch usa e getta, tieni a mente --abort, rispetta la regola d'oro, e trasformare "wip, fix typo, actually fix it" in una cronologia pulita diventerà routine. E se preferisci vedere il piano invece di modificare un file di testo, scarica GitSquid gratis e prova il rebase visuale sul tuo prossimo branch di feature.