Riscrivere la cronologia con Git Rebase

Nel flusso di lavoro fondamentale di Git, sviluppi una nuova funzione in un ramo argomento dedicato, quindi uniscilo in un ramo di produzione una volta terminato. Questo fa git si fondono uno strumento integrale per combinare i rami. Tuttavia, non è l'unico che Git offre.

Combinare i rami unendoli insieme

In alternativa allo scenario di cui sopra, è possibile combinare i rami con il git rebase comando. Invece di legare i rami con un commit di unione, ribasamento sposta l'intero ramo della funzione sulla punta di maestro come mostrato di seguito.

Combinare rami con git rebase

Questo ha lo stesso scopo di git si fondono, integrare i commit da diversi rami. Ma ci sono due motivi per cui potremmo desiderare di optare per un rebase su un'unione:

  • Risulta in una cronologia del progetto lineare.
  • Ci dà l'opportunità di ripulire i commit locali.

In questo tutorial, esploreremo questi due casi di utilizzo comune di git rebase. Sfortunatamente, i benefici di git rebase venire a un trade-off. Se utilizzato in modo errato, può essere una delle operazioni più pericolose che è possibile eseguire in un repository Git. Quindi, daremo anche uno sguardo attento ai pericoli della ribasatura.

Prerequisiti

Questo tutorial presume che tu abbia familiarità con i comandi Git di base e i flussi di lavoro di collaborazione. Dovresti essere comodo mettere in scena e commettere istantanee, sviluppare funzionalità in rami isolati, unire rami e spingere / tirare rami da / verso repository remoti.

1. Rebasing per una storia lineare

Il primo caso d'uso che esploreremo riguarda una storia di progetto divergente. Considera un repository in cui il tuo ramo di produzione si è spostato mentre stavi sviluppando una funzionalità:

Per rebase il caratteristica ramo sul maestro ramo, dovresti eseguire i seguenti comandi:

git checkout feature git rebase master

Questo trapianta il caratteristica ramo dalla sua posizione attuale alla punta del maestro ramo:

Ci sono due scenari in cui vorresti farlo. Innanzitutto, se la funzione si basa sul nuovo commit in maestro, ora avrebbe accesso a loro. In secondo luogo, se la funzionalità fosse completa, ora verrebbe configurata per l'unione rapida in avanti maestro. In entrambi i casi, i risultati di rebasing in una cronologia lineare, mentre git si fondono comporterebbe inutili commit di unione.

Ad esempio, considera cosa accadrebbe se integri il commit upstream con un'unione anziché un rebase:

git checkout feature git merge master 

Questo ci avrebbe dato un ulteriore commit di merge in caratteristica ramo. Inoltre, ciò accadrebbe ogni volta che volessi incorporare i commit upstream nella tua funzione. Alla fine, la cronologia del tuo progetto sarebbe piena di commit di fusione senza significato.

L'integrazione di upstream si impegna con l'unione

Questo stesso vantaggio può essere visto quando si fonde nella direzione opposta. Senza un rebase, integrando il finito caratteristica ramo in maestro richiede un merge commit. Sebbene si tratti di un commit merge significativo (nel senso che rappresenta una funzionalità completata), la cronologia risultante è piena di fork:

Integrazione di una funzionalità completata con un'unione

Quando esegui il rebase prima della fusione, Git è in grado di avanzare rapidamente maestro alla punta di caratteristica. Troverai una storia lineare di come il tuo progetto è progredito nel log git uscita: il commit in caratteristica sono ordinatamente raggruppati in cima ai commit in maestro. Questo non è necessariamente il caso quando i rami sono legati insieme con un commit di unione.

Rifondazione prima della fusione

Risolvere i conflitti

Quando corri git rebase, Git prende ogni commit nel ramo e li sposta, uno alla volta, sulla nuova base. Se uno di questi commuta altera la stessa linea (e) di codice che l'upstream commette, provocherà un conflitto.

Il git si fondono comando ti consente di risolvere tutti i conflitti del ramo alla fine dell'unione, che è uno degli scopi principali di un commit di unione. Tuttavia, funziona un po 'diversamente quando si ridefinisce. I conflitti vengono risolti in base al commit. Quindi se git rebase trova un conflitto, interromperà la procedura di rebase e visualizzerà un messaggio di avviso:

Fusione automatica readme.txt CONFLICT (contenuto): Unisci conflitto in readme.txt Impossibile unire le modifiche ... Dopo aver risolto questo problema, eseguire "git rebase --continue". Se preferisci saltare questa patch, lancia "git rebase --skip". Per controllare il ramo originale e smettere di rebasare, esegui "git rebase --abort". 

Visivamente, questa è la storia del tuo progetto quando git rebase incontra un conflitto:

I conflitti possono essere controllati eseguendo stato git. L'output sembra molto simile a un conflitto di unione:

Percorsi non raggruppati: (utilizzare "git reset HEAD ... "per togliere il palco) (usa" git add ... "per contrassegnare la risoluzione) entrambi modificati: readme.txt nessuna modifica aggiunta al commit (utilizzare" git add "e / o" git commit -a ") 

Per risolvere il conflitto, apri il file in conflitto (readme.txt nell'esempio precedente), trova le linee interessate e modificale manualmente sul risultato desiderato. Quindi, comunica a Git che il conflitto viene risolto mettendo in scena il file:

git aggiungi readme.txt 

Si noti che questo è esattamente lo stesso modo in cui si segna a git si fondono conflitto come risolto. Ma ricorda che sei nel mezzo di un rebase - non vuoi dimenticare il resto dei commit che devono essere spostati. L'ultimo passo è dire a Git di finire con il rebasing --Continua opzione:

git rebase --continue 

Questo sposterà il resto dei commit, uno alla volta, e se sorgono altri conflitti, dovrai ripetere questo processo da capo.

Se non vuoi risolvere il conflitto, puoi optare per il --Salta o --abortire bandiere. Quest'ultimo è particolarmente utile se non hai idea di cosa sta succedendo e vuoi solo tornare in sicurezza.

# Ignora il commit che ha causato il conflitto git rebase --skip # Interrompe l'intero rebase e torna alla scheda di disegno git rebase --abort 

2. Rifondazione per ripulire i commit locali

Finora, abbiamo solo usato git rebase spostare rami, ma è molto più potente di quello. Passando il -io flag, è possibile iniziare una sessione di rebasing interattiva. Il rebasing interattivo consente di definire con precisione come ogni commit verrà spostato nella nuova base. Questo ti dà l'opportunità di ripulire la cronologia di una funzione prima di condividerla con altri sviluppatori.

Ad esempio, diciamo che hai finito di lavorare sul tuo caratteristica filiale e tu sei pronto per integrarlo maestro. Per iniziare una sessione di rebasing interattiva, eseguire il seguente comando:

git checkout feature git rebase -i master 

Questo aprirà un editor contenente tutti i commit in caratteristica che stanno per essere spostati:

selezionare 5c43c2b [Descrizione per il commit più vecchio] selezionare b8f3240 [Descrizione per il secondo commit più vecchio] selezionare c069f4a [Descrizione per il commit più recente] 

Questo elenco definisce ciò che il caratteristica il ramo avrà l'aspetto dopo il rebase. Ogni riga rappresenta un commit e il raccogliere comando prima che ogni hash del commit definisca cosa gli succederà durante il rebase. Si noti che i commit sono elencati dal più vecchio al più recente. Modificando questo elenco, ottieni il controllo completo sulla cronologia del tuo progetto.

Se si desidera modificare l'ordine dei commit, è sufficiente riordinare le righe. Se vuoi cambiare il messaggio di un commit, usa il esprimere con altre parole comando. Se si desidera combinare due commit, modificare il raccogliere comando a schiacciare. Questo farà rotolare tutte le modifiche in quel commit in quella sopra. Ad esempio, se hai schiacciato il secondo commit nell'elenco precedente, il caratteristica ramo sarebbe simile al seguente dopo aver salvato e chiuso l'editor:

Schiacciare il secondo commit con un rebase interattivo

Il modificare il comando è particolarmente potente. Quando raggiunge il commit specificato, Git interromperà la procedura di rebase, proprio come quando incontra un conflitto. Questo ti dà l'opportunità di modificare il contenuto del commit con git commit --amend o addirittura aggiungere più commit con lo standard aggiungi git/Git commit comandi. Ogni nuovo commit che aggiungi farà parte del nuovo ramo.

Il rebasing interattivo può avere un impatto profondo sul flusso di lavoro dello sviluppo. Invece di preoccuparti di suddividere le tue modifiche in commit incapsulati, puoi concentrarti sulla scrittura del tuo codice. Se si finisce per commettere ciò che dovrebbe essere una singola modifica in quattro snapshot separati, allora non si tratta di un problema: riscrivere la cronologia con git rebase -i e li schiaccia tutti in un unico impegno significativo.

3. Pericoli di ribasso

Ora che hai una comprensione git rebase, possiamo parlare di quando non usarlo. Internamente, il rebasing non sposta effettivamente i commit in un nuovo ramo. Invece, crea nuovi commit che contengono le modifiche desiderate. Con questa idea, il rebasing è meglio visualizzato come segue:

Dopo il rebase, il commit in caratteristica avrà diversi hash di commit. Ciò significa che non abbiamo semplicemente riposizionato un ramo: abbiamo letteralmente riscritto la cronologia del nostro progetto. Questo è un effetto collaterale molto importante di git rebase.

Quando lavori da solo su un progetto, riscrivere la cronologia non è un grosso problema. Tuttavia, non appena inizi a lavorare in un ambiente collaborativo, può diventare molto pericoloso. Se riscrivi le commit che altri sviluppatori stanno utilizzando (ad es., Commette su maestro ramo), sembrerà come se quei commissari scomparissero la prossima volta che provano a tirare dentro il tuo lavoro. Ciò si traduce in uno scenario di confusione difficile da recuperare.

Con questa idea, non dovresti mai rebase i commit che sono stati spinti in un repository pubblico a meno che tu non sia sicuro che nessuno abbia basato il loro lavoro su di loro.

Conclusione

Questo tutorial ha introdotto i due casi d'uso più comuni di git rebase. Abbiamo parlato molto di come spostare le filiali in giro, ma teniamo presente che la ridefinizione riguarda davvero il controllo della cronologia del progetto. Il potere di riscrivere i commit dopo il fatto ti libera di concentrarti sulle attività di sviluppo invece di suddividere il tuo lavoro in istantanee isolate.

Tieni presente che il rebasing è un'aggiunta completamente opzionale alla tua casella degli strumenti Git. Puoi sempre fare tutto il necessario con plain old git si fondono comandi. In effetti, questo è più sicuro in quanto evita la possibilità di riscrivere la storia pubblica. Tuttavia, se comprendi i rischi, git rebase può essere un modo molto più semplice per integrare i rami rispetto alla fusione dei commit.