back to top

Risolvere conflitti e pulire la history: la guida a git rebase –interactive per Frontend Developer

Nel mondo dello sviluppo frontend moderno, la collaborazione e la gestione del codice sono fondamentali. Git è lo standard de facto per il controllo di versione, ma padroneggiarne i flussi di lavoro più avanzati può fare la differenza tra un progetto ordinato e uno caotico. Uno strumento potente, spesso temuto ma incredibilmente utile, è git rebase --interactive. Se hai mai desiderato una history dei commit più pulita o hai sudato freddo di fronte a un conflitto di merge complesso, questa guida fa per te.

Impareremo cos’è git rebase --interactive, quando è opportuno usarlo (e quando non farlo), e soprattutto come sfruttarlo per riscrivere la storia dei tuoi commit locali e risolvere conflitti in modo chirurgico, mantenendo il tuo branch di sviluppo snello e comprensibile. Preparati a portare le tue skill di Git al livello successivo!

Se sei nuovo a Git o vuoi rinfrescare le basi, ti consiglio di dare un’occhiata alla nostra guida definitiva a Git & GitHub per team frontend.

Cos’è git rebase? Una breve introduzione

Prima di tuffarci nella modalità interattiva, capiamo il concetto base di git rebase. In parole semplici, rebase (ribasare) permette di prendere una serie di commit da un branch e “riapplicarli” sopra un altro commit (solitamente la punta di un altro branch, come main o develop).

Immagina di aver creato un branch feature/nuova-interfaccia partendo da main. Mentre lavoravi sulla tua feature, altri sviluppatori hanno aggiunto nuovi commit a main.

      A---B---C feature/nuova-interfaccia
     /
D---E---F---G main

Usando git rebase main mentre sei sul branch feature/nuova-interfaccia, Git prenderà i commit A, B, e C, li metterà temporaneamente da parte, aggiornerà il tuo branch all’ultimo commit di main (G), e poi riapplicherà A, B, e C uno alla volta sopra G.

              A'--B'--C' feature/nuova-interfaccia
             /
D---E---F---G main

Il risultato è una history lineare: sembra che tu abbia iniziato a lavorare sulla tua feature dopo l’ultimo aggiornamento di main. Questo contrasta con git merge, che creerebbe un “merge commit” per unire le due storie:

      A---B---C --------- M feature/nuova-interfaccia
     /                   /
D---E---F---G ----------- main

Il rebase produce una history più pulita e facile da seguire, ma attenzione: riscrive la storia dei commit (nota come A', B', C' sono nuovi commit, anche se contengono le stesse modifiche di A, B, C).

Entra in scena git rebase --interactive (o git rebase -i)

La vera magia inizia con l’opzione --interactive (abbreviata in -i). Mentre un git rebase standard riapplica semplicemente i commit, git rebase --interactive ti dà il controllo prima che i commit vengano riapplicati. Ti permette di modificare la serie di commit che stai per ribasare.

Quando esegui un comando come git rebase -i HEAD~3 (ribasa interattivamente gli ultimi 3 commit) o git rebase -i main (ribasa interattivamente il tuo branch corrente su main), Git apre il tuo editor di testo predefinito con un file che elenca i commit coinvolti e le azioni che puoi intraprendere per ciascuno:

pick f7f3f6d Aggiunta funzione X
pick 310154e Correzione bug Y
pick a5f4a0d Refactor componente Z

# Rebase 12a3b4c..a5f4a0d onto 12a3b4c (3 commands)
#
# Comandi:
# p, pick <commit> = usa il commit
# r, reword <commit> = usa il commit, ma modifica il messaggio
# e, edit <commit> = usa il commit, ma fermati per fare modifiche
# s, squash <commit> = usa il commit, ma fondilo nel commit precedente
# f, fixup <commit> = come "squash", ma scarta il messaggio di questo commit
# x, exec <command> = esegui un comando di shell
# b, break = fermati qui (continua poi con 'git rebase --continue')
# d, drop <commit> = rimuovi il commit
# l, label <label> = assegna un nome a questo punto nella history
# t, reset <label> = resetta HEAD a un label precedente
# m, merge [-C <commit> | -c <commit>] <branch> = crea un merge commit
#
# Questi commit vengono riapplicati uno alla volta sulla base del commit originale.
# Se rimuovi una riga qui, QUEL COMMIT ANDRÀ PERSO.
# Tuttavia, se rimuovi tutto, il rebase verrà annullato.
#

Questo ti dà un potere enorme per:

  1. Riordinare i commit: Cambia l’ordine delle righe pick.
  2. Modificare i messaggi: Cambia pick in reword (r).
  3. Unire commit: Cambia pick in squash (s) o fixup (f). squash ti permette di combinare i messaggi, fixup scarta il messaggio del commit che stai fondendo. Utile per unire piccole correzioni (“fix typo”, “WIP”) in commit più significativi.
  4. Dividere commit: Usa edit (e) per fermarti a un certo commit, fare modifiche (es. git reset HEAD^, poi git add -p e git commit multipli), e infine git rebase --continue.
  5. Rimuovere commit: Cancella la riga o usa drop (d). Fai attenzione!
  6. Modificare il codice di un commit: Usa edit (e), apporta le modifiche, fai git add . e poi git commit --amend, infine git rebase --continue.

Quando e Perché Usare git rebase -i?

L’uso principale di git rebase -i è pulire la tua history di commit locale prima di condividerla (es. pushando su un repository remoto o aprendo una Pull Request).

Scenari ideali:

  1. Preparare una Pull Request: Prima di chiedere una revisione, puoi usare git rebase -i per:
    • Riordinare i commit in modo logico.
    • Unire piccoli commit di fix o WIP in commit più grandi e significativi.
    • Riscrivere messaggi di commit poco chiari.
    • Assicurarti che la tua feature branch sia aggiornata rispetto al branch principale (main o develop) con una history lineare.
  2. Correggere errori: Ti accorgi di un errore in un commit precedente (ma non ancora pushato)? Puoi usare edit per correggerlo.
  3. Organizzare il lavoro: Hai fatto diversi commit disordinati durante lo sviluppo? rebase -i ti aiuta a presentarli in modo professionale.

La Regola d’Oro del Rebase:

Non ribasare MAI commit che sono già stati pushati su un branch condiviso (usato da altri).

Riscrivere la storia di un branch pubblico causa enormi problemi ai tuoi collaboratori, che avranno copie della “vecchia” storia. Quando faranno git pull, Git vedrà storie divergenti e creerà confusione. Per integrare modifiche da un branch condiviso nel tuo lavoro, usa git merge o git pull (che spesso fa un merge per default). Il rebase è principalmente per la tua storia locale.

Risolvere Conflitti durante git rebase --interactive

Ecco il cuore della questione. I conflitti durante un rebase (interattivo o meno) accadono quando Git tenta di riapplicare un tuo commit che modifica le stesse linee di codice cambiate nei commit “sottostanti” (quelli del branch su cui stai ribasando, es. main).

Supponiamo di avere questa situazione:

      A---B---C feature/mia-feature  (Tu hai modificato il file `style.css` nel commit B)
     /
D---E---F---G main                 (Qualcun altro ha modificato `style.css` nel commit F)

Decidi di ribasare la tua feature su main:

git checkout feature/mia-feature
git rebase -i main

Git prova a riapplicare i commit A, B, C sopra G. Quando arriva a riapplicare il tuo commit B (che ha modificato style.css), si accorge che anche il commit F (ora parte della storia sottostante) ha modificato le stesse righe in style.css. Conflitto!

Git si ferma e ti informa:

Auto-merging style.css
CONFLICT (content): Merge conflict in style.css
error: could not apply B... Descrizione del commit B
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add <conflicted_file>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply B... Descrizione del commit B

Cosa fare? Ecco la procedura passo-passo:

Identifica i file in conflitto: Il messaggio di Git te lo dice (CONFLICT (content): Merge conflict in style.css). Puoi anche usare git status:

git status

Output (esempio):

interactive rebase in progress; onto abc1234
Last command done (1 command done):
   pick A Descrizione commit A
Next command to do (1 remaining command):
   pick B Descrizione commit B
You are currently rebasing branch 'feature/mia-feature' on 'abc1234'.
  (fix conflicts and then run "git rebase --continue")
  (use "git rebase --skip" to skip this patch)
  (use "git rebase --abort" to check out the original branch)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
        both modified:   style.css

no changes added to commit (use "git add" and/or "git commit -a")

La sezione Unmerged paths elenca i file con conflitti.

Apri il file in conflitto: Apri style.css nel tuo editor preferito. Troverai dei marcatori speciali inseriti da Git:

/* Stili per il bottone principale */
.button-primary {
<<<<<<< HEAD
  background-color: #007bff; /* Modifica da main (commit F) */
  color: white;
=======
  background-color: #0056b3; /* Tua modifica (commit B) */
  color: #ffffff;
  padding: 10px 20px; /* Hai aggiunto anche padding */
>>>>>>> B... Descrizione del commit B
  border: none;
  border-radius: 5px;
}

<<<<<<< HEAD: Indica l’inizio delle modifiche provenienti dal branch su cui stai ribasando (in questo caso, main, rappresentato da HEAD durante il rebase).

=======: Separa le due versioni conflittuali.

>>>>>>> B... Descrizione commit B: Indica la fine delle modifiche provenienti dal tuo commit che Git sta cercando di riapplicare.

Risolvi manualmente il conflitto: Questo è il passaggio cruciale e richiede la tua logica di sviluppatore. Devi decidere quale codice tenere, se combinare le modifiche, o se scrivere qualcosa di completamente nuovo. Devi rimuovere i marcatori <<<<<<<, =======, >>>>>>> e lasciare solo il codice finale desiderato.

  • Scenario 1: Tieni solo le tue modifiche: Rimuovi la parte tra <<<<<<< HEAD e =======, e i marcatori.

  • Scenario 2: Tieni solo le modifiche da main: Rimuovi la parte tra ======= e >>>>>>> ..., e i marcatori.

  • Scenario 3: Combina le modifiche: Modifica il codice per integrare entrambe le versioni in modo sensato, poi rimuovi i marcatori.
Esempio di risoluzione (combinando):

/* Stili per il bottone principale */
.button-primary {
  /* Combiniamo: usiamo il colore di main, ma teniamo il padding aggiunto */
  background-color: #007bff; /* Modifica da main (commit F) */
  color: white;
  padding: 10px 20px; /* Tua modifica (commit B) */
  border: none;
  border-radius: 5px;
}

Aggiungi il file risolto allo staging: Una volta che sei soddisfatto della risoluzione, devi comunicarlo a Git:

git add style.css

Se ci sono più file in conflitto, risolvili tutti e fai git add per ciascuno.

Continua il rebase: Ora che hai risolto i conflitti per questo specifico commit, puoi dire a Git di continuare a riapplicare i commit successivi:

git rebase --continue

Ripeti se necessario: Il rebase potrebbe fermarsi di nuovo se anche i commit successivi (nel nostro esempio, il commit C) generano conflitti con le modifiche presenti su main o con le risoluzioni appena fatte. Ripeti i passaggi 1-5 per ogni conflitto.

Completamento: Una volta che tutti i commit sono stati riapplicati con successo (con eventuali conflitti risolti), Git completerà il rebase e il tuo branch feature/mia-feature sarà ribasato su main con una history lineare e pulita.

Opzioni alternative durante un conflitto:

  • git rebase --abort: Se ti senti perso o hai fatto un errore, puoi annullare l’intero rebase e tornare allo stato precedente all’avvio del comando. È un’ancora di salvezza!
  • git rebase --skip: Usare con estrema cautela. Questo comando salta completamente l’applicazione del commit che ha causato il conflitto. Perderai le modifiche introdotte da quel commit. Utile solo se ti rendi conto che quel commit è diventato obsoleto o ridondante.

Padroneggiare la risoluzione dei conflitti durante un git rebase interactive è essenziale per mantenere un flusso di lavoro pulito ed efficiente.

Problemi Comuni e Come Evitarli

Anche i developer esperti possono incontrare difficoltà con git rebase -i. Ecco alcuni problemi comuni:

  1. Ribasare branch pubblici/condivisi:
    • Problema: Viola la Regola d’Oro. Causa divergenze nella history per gli altri collaboratori.
    • Soluzione: Non farlo. Usa git merge per integrare modifiche da branch condivisi. Usa rebase solo su branch locali o feature branch prima che vengano condivise/mergiate. Se hai assolutamente bisogno di correggere un commit già pushato (e sei sicuro che nessun altro ci stia lavorando), comunica chiaramente col team e preparati a usare git push --force-with-lease (più sicuro di --force).
  2. Perdere commit o modifiche:
    • Problema: Usare drop accidentalmente, fare errori durante lo squash/fixup, o risolvere male un conflitto e poi fare git rebase --skip.
    • Soluzione: Lavora con attenzione nell’editor interattivo. Prima di un rebase complesso, puoi creare un branch di backup (git branch backup-prima-rebase). Se perdi qualcosa, git reflog è il tuo migliore amico: mostra la history di tutte le azioni di HEAD e ti permette di recuperare commit “persi”.
  3. Rebase infiniti o molto complessi:
    • Problema: Tentare di ribasare un branch con moltissimi commit su un altro branch che è cambiato radicalmente. Potresti dover risolvere conflitti per decine di commit.
    • Soluzione: Fai rebase più spesso! Integra le modifiche dal branch principale (main/develop) nel tuo feature branch frequentemente (git pull --rebase origin main o git fetch origin && git rebase origin/main). Questo mantiene le differenze piccole e gestibili. Se un rebase diventa troppo complesso, git rebase --abort e considera un merge.
  4. Dimenticare git rebase --continue:
    • Problema: Risolvi i conflitti, fai git add, ma ti dimentichi di continuare. Lo stato del repository rimane “in rebase”.
    • Soluzione: git status ti ricorderà che sei in mezzo a un rebase. Esegui git rebase --continue.
  5. Committare i marcatori di conflitto:
    • Problema: Modifichi il file ma dimentichi di rimuovere i marcatori <<<<<<<, =======, >>>>>>> prima di fare git add e git rebase --continue.
    • Soluzione: Controlla sempre il file risolto prima di fare git add. Se te ne accorgi dopo, puoi usare git commit --amend (se il rebase è ancora fermo su quel commit) o un altro git rebase -i per correggere il commit successivo. Potrebbe essere utile anche uno strumento di merge grafico (come quello integrato in VS Code, WebStorm, o git mergetool).

git rebase -i vs git merge: Quale scegliere?

Non c’è una risposta unica, dipende dal contesto e dalle preferenze del team.

  • git rebase -i (prima di condividere):
    • Pro: History lineare e pulita, facile da leggere e navigare. Ogni commit sulla feature branch rappresenta un passo logico. Ideale per preparare Pull Request. Permette di “correggere” la storia locale.
    • Contro: Riscrive la storia (pericoloso su branch condivisi). I conflitti vanno risolti commit per commit, il che può essere tedioso se sono tanti. Non preserva il contesto esatto di quando il lavoro è stato fatto rispetto al branch principale.
  • git merge:
    • Pro: Non riscrive la storia, preservando il contesto originale. È sicuro da usare su qualsiasi branch. I conflitti vengono risolti una sola volta, al momento del merge.
    • Contro: Crea “merge commit” extra che possono rendere la history più complessa e difficile da leggere (soprattutto con molti branch paralleli). La history diventa un grafo, non una linea retta.

Molti team adottano un approccio ibrido:

  1. Gli sviluppatori usano git rebase -i sui loro feature branch locali per tenerli aggiornati con main/develop e per pulire la history prima di una Pull Request.
  2. Quando la feature è pronta e approvata, viene integrata nel branch principale usando git merge --no-ff (per preservare il contesto della feature branch e creare comunque un merge commit esplicito) oppure con l’opzione “Squash and merge” offerta da piattaforme come GitHub/GitLab (che prende tutti i commit della feature, li unisce in uno solo, e lo applica su main).

Per approfondire la gestione dei progetti frontend con Git e GitHub, leggi la nostra guida su come usare GitHub per gestire progetti frontend.

Conclusione: Abbraccia il Potere del Rebase Interattivo

git rebase --interactive è uno strumento incredibilmente potente nel toolkit di ogni sviluppatore frontend moderno. Anche se all’inizio può sembrare intimidatorio, specialmente quando si presentano conflitti, padroneggiarlo porta a enormi benefici: una history dei commit pulita, Pull Request più facili da revisionare e una migliore comprensione del flusso di lavoro di Git.

Ricorda la Regola d’Oro, fai rebase spesso sui tuoi branch locali, e non aver paura di affrontare i conflitti passo dopo passo. Con un po’ di pratica, scoprirai che git rebase interactive non è un nemico da temere, ma un prezioso alleato per mantenere il tuo codice e la tua collaborazione organizzati ed efficienti. Buona riscrittura della storia (locale)!

Condividi

Articoli Recenti

Categorie popolari