Spiegazione del flusso Git migliorato
Pubblicato: 2022-03-11Causare inavvertitamente danni con Git può essere fin troppo facile. Tuttavia, il modo migliore per utilizzare Git sarà sempre controverso.
Questo perché Git stesso descrive in dettaglio solo le operazioni di ramificazione di base, il che lascia i suoi modelli di utilizzo, ovvero i modelli di ramificazione, una questione di opinione dell'utente. I modelli di ramificazione Git promettono di alleviare il dolore organizzando il caos che inevitabilmente sorge quando gli sviluppatori di software apportano modifiche alle loro basi di codice.
Come molti sviluppatori, volevi qualcosa che "funzionasse" in modo da poter andare avanti con lo sviluppo del software effettivo. Quindi hai scelto Git flow , un modello di ramificazione spesso consigliato agli utenti di Git. Forse all'inizio eri d'accordo con la logica di Git flow, fino a quando non hai riscontrato alcuni ostacoli in pratica. O forse Git flow non sembra adatto a te per adottarlo. Dopotutto, ci sono innumerevoli variabili in gioco e nessun singolo modello di ramificazione funzionerà bene in ogni situazione.
Buone notizie! Una variazione del classico modello di flusso Git, il flusso Git migliorato semplifica le manovre più comuni del flusso di lavoro Git pur mantenendo i principali vantaggi.
Lo splendore e la miseria del classico modello Git Flow
Sono stato un forte sostenitore di Git flow da quando ho scoperto come eccelle nello sviluppo di un prodotto che si evolve con incrementi di valore significativi (in altre parole, rilasci ).
Un significativo incremento di valore richiede una notevole quantità di tempo per essere completato, come gli sprint di oltre due settimane tipicamente utilizzati nello sviluppo basato su Scrum. Se un team di sviluppo è già stato distribuito alla produzione, potrebbero verificarsi problemi se l'ambito della versione successiva si accumula nello stesso punto in cui risiede il codice di produzione, ad esempio nel ramo principale del repository Git che utilizza.
Mentre il prodotto è ancora nella fase di sviluppo iniziale, ovvero non c'è produzione e non ci sono utenti reali del prodotto, va bene che il team tenga semplicemente tutto all'interno del ramo principale. In effetti, va più che bene: questa strategia consente il ritmo di sviluppo più veloce senza troppe cerimonie. Ma le cose cambiano in un ambiente di produzione; quindi, le persone reali iniziano a fare affidamento sulla stabilità del prodotto.
Ad esempio, se c'è un bug critico in produzione che deve essere corretto immediatamente, sarebbe un grave disastro per il team di sviluppo dover ripristinare tutto il lavoro accumulato finora nel ramo principale solo per distribuire la correzione. E la distribuzione del codice senza un test adeguato, indipendentemente dal fatto che il codice sia considerato semilavorato o ben sviluppato, non è chiaramente un'opzione.
È qui che brillano i modelli ramificati, incluso Git flow. Qualsiasi modello di ramificazione sofisticato dovrebbe rispondere a domande su come isolare la versione successiva dalla versione del sistema attualmente utilizzata dalle persone, come aggiornare quella versione con la versione successiva e come introdurre correzioni di eventuali bug critici nella versione corrente.
Il processo di flusso Git affronta questi scenari fondamentali separando "main" (il ramo di produzione o "versione corrente") e "develop" (il ramo di sviluppo o "rilascio successivo") e fornendo tutte le regole sull'utilizzo dei rami di funzionalità/rilascio/hotfix . Risolve efficacemente molti mal di testa dai flussi di lavoro di sviluppo di prodotti basati su release.
Ma anche con progetti adatti al classico modello di flusso Git, ho subito i tipici problemi che può portare:
- Il flusso di Git è complesso, con due rami di lunga durata, tre tipi di rami temporanei e regole rigide su come i rami si relazionano tra loro. Tale complessità rende più probabili gli errori e aumenta lo sforzo necessario per risolverli.
- I rami di rilascio e aggiornamento rapido richiedono la "doppia fusione": una volta in principale, quindi in sviluppo. A volte puoi dimenticare di fare entrambe le cose. Puoi semplificare la ramificazione del flusso Git con script o plug-in client della GUI VCS, ma devi prima configurarli per ogni macchina di ogni sviluppatore coinvolto in un determinato progetto.
- Nei flussi di lavoro CI/CD, di solito si ottengono due build finali per una versione, una dall'ultimo commit del ramo di rilascio stesso e un'altra dal commit di unione a main. A rigor di termini, dovresti usare quello principale, ma di solito i due sono identici, creando il potenziale di confusione.
Inserisci "Flusso Git avanzato"
La prima volta che ho utilizzato un flusso Git avanzato è stato su un progetto greenfield a codice chiuso. Stavo lavorando con un altro sviluppatore e abbiamo lavorato al progetto impegnandoci direttamente nel ramo principale.
Nota: fino alla prima versione pubblica di un prodotto, ha assolutamente senso eseguire il commit di tutte le modifiche direttamente nel ramo principale, anche se sei un sostenitore del flusso Git, per il bene della velocità e della semplicità del flusso di lavoro di sviluppo. Dal momento che non c'è ancora produzione, non c'è possibilità di un bug di produzione che il team deve correggere il prima possibile. Fare tutta la magia ramificata che il classico flusso Git implica è quindi eccessivo in questa fase.
Poi ci siamo avvicinati al rilascio iniziale e abbiamo convenuto che, oltre quel punto, non saremmo più stati a nostro agio a impegnarci direttamente nel ramo principale. Ci siamo mossi abbastanza rapidamente e le priorità aziendali non hanno lasciato molto spazio per stabilire un processo di sviluppo solido come una roccia, ovvero uno con test automatizzati sufficienti per darci la certezza che il nostro ramo principale fosse pronto per il rilascio.
Sembrava essere un caso valido per il classico modello Git flow. Con le filiali principali e di sviluppo separate e un tempo sufficiente tra incrementi di valore significativi, c'era la certezza che il QA principalmente manuale avrebbe prodotto risultati sufficientemente buoni. Quando ho sostenuto il flusso di Git, il mio collega ha suggerito qualcosa di simile, ma con alcune differenze chiave.
All'inizio, ho respinto. Mi sembrava che alcune delle "patch" proposte per il flusso Git classico fossero un po' troppo rivoluzionarie. Ho pensato che avrebbero potuto rompere l'idea principale e l'intero approccio sarebbe fallito. Ma con ulteriori riflessioni, mi sono reso conto che queste modifiche in realtà non interrompono il flusso di Git. Nel frattempo, lo rendono un modello di ramificazione Git migliore risolvendo tutti i punti dolenti sopra menzionati.
Dopo il successo con l'approccio modificato in quel progetto, l'ho usato in un altro progetto closed-source con un piccolo team dietro, in cui ero il proprietario permanente della base di codice e uno o due sviluppatori in outsourcing mi aiutavano di tanto in tanto. In questo progetto, siamo passati alla produzione per sei mesi e da allora utilizziamo i test CI ed E2E da oltre un anno, con rilasci ogni mese circa.
La mia esperienza complessiva con questo nuovo approccio di ramificazione è stata così positiva che ho voluto condividerla con i miei colleghi sviluppatori per aiutarli a aggirare gli svantaggi del flusso Git classico.
Somiglianze con Git Flow classico: isolamento dello sviluppo
Per l'isolamento del lavoro nel flusso Git avanzato, sono ancora presenti due rami di lunga durata, principale e sviluppato. (Gli utenti hanno ancora funzionalità di aggiornamento rapido e rilascio, con un'enfasi sulle "capacità", poiché queste non sono più ramificazioni. Entreremo nei dettagli nella sezione differenze.)
Non esiste uno schema di denominazione ufficiale per i rami delle funzionalità di flusso Git classici. Devi semplicemente uscire dallo sviluppo e unirti di nuovo allo sviluppo quando la funzionalità è pronta. I team possono utilizzare qualsiasi convenzione di denominazione che desiderano o semplicemente sperare che gli sviluppatori utilizzino nomi più descrittivi di "my-branch". Lo stesso vale per il flusso Git avanzato.
Tutte le funzionalità accumulate nel ramo di sviluppo fino a un punto di interruzione daranno forma alla nuova versione.
Fusioni di zucca
Raccomando caldamente di usare l'unione di squash per i rami delle funzionalità per mantenere la cronologia piacevole e lineare per la maggior parte del tempo. Senza di esso, i grafici di commit (dagli strumenti della GUI o git log --graph
) iniziano a sembrare sciatti quando un team si destreggia anche con una manciata di rami di funzionalità:
Ma anche se sei d'accordo con la grafica in questo scenario, c'è un altro motivo per schiacciare. Senza schiacciare, le visualizzazioni della cronologia dei commit, tra cui sia git log
semplice (senza --graph
) che anche GitHub, raccontano storie piuttosto incoerenti anche con il più semplice degli scenari di unione:
L'avvertenza sull'utilizzo dell'unione di squash è che la cronologia del ramo di funzionalità originale viene persa. Ma questo avvertimento non si applica nemmeno se stai usando GitHub, ad esempio, che espone la cronologia originale completa di un ramo di funzionalità tramite la richiesta pull che è stata unita a compressione, anche dopo che il ramo di funzionalità stesso è stato eliminato.
Differenze rispetto al flusso Git classico: versioni e hotfix
Esaminiamo il ciclo di rilascio poiché (si spera) è la cosa principale che farai. Quando arriviamo al punto in cui vorremmo rilasciare ciò che è stato accumulato in fase di sviluppo, è strettamente un superset di main. Dopodiché, è qui che iniziano le maggiori differenze tra il flusso Git classico e avanzato.

Rilascio in Enhanced Git Flow
Ogni fase della creazione di un rilascio con un flusso Git avanzato differisce dal classico processo di flusso Git:
- Le versioni si basano su main, piuttosto che su sviluppo. Contrassegna la punta corrente del ramo principale con qualcosa di significativo. Ho adottato i tag in base alla data corrente nel formato ISO 8601 preceduto da una "v", ad esempio v2020-09-09 .
- Se si verificano più versioni in un giorno, ad esempio hotfix, il formato potrebbe avere un numero sequenziale o una lettera appiccicata, se necessario.
- Tieni presente che i tag, in generale, non corrispondono alle date di rilascio. Servono esclusivamente a costringere Git a mantenere un riferimento all'aspetto del ramo principale al momento dell'inizio del successivo processo di rilascio.
- Spingi il tag usando
git push origin <the new tag name>
. - Dopodiché, una piccola sorpresa: elimina il tuo ramo principale locale . Non preoccuparti, perché lo ripristineremo a breve.
- Tutti i commit su main sono ancora al sicuro: li abbiamo protetti dalla raccolta dei rifiuti contrassegnando main nel passaggio precedente. Ognuno di questi commit, anche gli hotfix, come tratteremo a breve, fa anche parte dello sviluppo.
- Assicurati solo che solo una persona in una squadra lo stia facendo per ogni dato rilascio; questo è il cosiddetto ruolo di "responsabile del rilascio". Un responsabile del rilascio è solitamente il membro più esperto e/o più anziano del team, ma un team farebbe bene a evitare che un particolare membro del team assuma questo ruolo in modo permanente. Ha più senso diffondere la conoscenza tra il team per aumentare il famigerato fattore bus.
- Crea un nuovo ramo principale locale al tip commit del tuo ramo di sviluppo .
- Spingi questa nuova struttura usando
git push --force
, dal momento che il repository remoto non accetterà un "cambiamento così drastico" così facilmente. Ancora una volta, questo non è così pericoloso come potrebbe sembrare in questo contesto perché:- Stiamo semplicemente spostando il puntatore del ramo principale da un commit all'altro.
- Solo un particolare membro del team esegue questa modifica alla volta.
- Il lavoro di sviluppo quotidiano avviene nel ramo di sviluppo, quindi non interromperai il lavoro di nessuno spostandoti in questo modo.
- Hai la tua nuova versione! Distribuiscilo nell'ambiente di staging e testalo. (Discuteremo di seguito i modelli CI/CD convenienti.) Qualsiasi correzione va direttamente al ramo principale e per questo motivo inizierà a divergere dal ramo di sviluppo.
- Allo stesso tempo, puoi iniziare a lavorare su una nuova versione nel ramo di sviluppo, lo stesso vantaggio visto nel flusso Git classico.
- Nel malaugurato caso in cui sia necessario un hotfix per ciò che è attualmente in produzione (non il rilascio imminente in staging) a questo punto, sono disponibili ulteriori dettagli su questo scenario in "Gestione degli hotfix durante un rilascio attivo..." di seguito.
- Quando la tua nuova versione è considerata sufficientemente stabile, distribuisci la versione finale nell'ambiente di produzione ed esegui un singolo unione di compressione di main per sviluppare per raccogliere tutte le correzioni.
Correzioni rapide nel flusso Git avanzato
I casi di hotfix sono duplici. Se stai eseguendo un hotfix quando non c'è una versione attiva , ad esempio, il team sta preparando una nuova versione nel ramo di sviluppo, è un gioco da ragazzi: esegui il commit su main, fai distribuire e testare le modifiche nello staging finché non sono pronte, quindi distribuire alla produzione.
Come ultimo passaggio, seleziona il tuo commit da main a development per assicurarti che il prossimo rilascio contenga tutte le correzioni. Nel caso in cui ti ritrovi con diversi commit di hotfix, risparmi fatica, specialmente se il tuo IDE o altro strumento Git può facilitarlo, creando e applicando una patch invece di selezionare più volte. Cercare di schiacciare merge main per sviluppare dopo il rilascio iniziale rischia di finire con conflitti con i progressi indipendenti fatti nel ramo di sviluppo, quindi non lo consiglio.
La gestione degli hotfix durante una versione attiva , ovvero quando si forza il push principale e si sta ancora preparando la nuova versione, è la parte più debole del flusso Git avanzato. A seconda della durata del ciclo di rilascio e della gravità del problema che devi risolvere, punta sempre a includere le correzioni nella nuova versione stessa: è la strada più semplice da percorrere e non interrompe affatto il flusso di lavoro generale.
Nel caso in cui sia un no-go, devi introdurre una correzione rapidamente e non puoi aspettare che la nuova versione sia pronta, allora preparati per una procedura Git piuttosto intricata:
- Crea un ramo, lo chiameremo "nuova versione", ma il tuo team può adottare qualsiasi convenzione di denominazione qui, allo stesso commit dell'attuale suggerimento di main. Spingi la nuova versione.
- Elimina e ricrea il ramo principale locale al commit del tag creato in precedenza per la versione attiva corrente. Forza spinta principale.
- Introdurre le correzioni necessarie per main, distribuire nell'ambiente di staging e testare. Quando è pronto, esegui il deployment in produzione.
- Propaga le modifiche dall'attuale versione principale alla nuova versione tramite cherry-picking o una patch.
- Dopodiché, ripetere la procedura di rilascio: taggare la punta del main corrente e spingere il tag, eliminare e ricreare il main locale sulla punta del ramo della nuova versione e forzare push main.
- Probabilmente non avrai bisogno del tag precedente, quindi puoi rimuoverlo.
- Il ramo della nuova versione ora è ridondante, quindi puoi rimuoverlo anche tu.
- Ora dovresti essere pronto per andare come al solito con una nuova versione. Concludi propagando gli hotfix di emergenza da principale a sviluppare tramite cherry-picking o una patch.
Con una pianificazione adeguata, una qualità del codice sufficientemente elevata e uno sviluppo sano e una cultura del controllo di qualità, è improbabile che il tuo team debba utilizzare questo metodo. È stato prudente sviluppare e testare un tale piano di emergenza per un flusso Git migliorato, per ogni evenienza, ma non ho mai avuto bisogno di usarlo nella pratica.
Configurazione CI/CD su Enhanced Git Flow
Non tutti i progetti richiedono un ambiente di sviluppo dedicato. Potrebbe essere abbastanza facile configurare un sofisticato ambiente di sviluppo locale su ogni macchina di sviluppo.
Ma un ambiente di sviluppo dedicato può contribuire a una cultura dello sviluppo più sana. L'esecuzione di test, la misurazione della copertura dei test e il calcolo delle metriche di complessità sul ramo di sviluppo spesso riduce il costo degli errori rilevandoli molto prima che finiscano nella fase iniziale.
Ho trovato alcuni pattern CI/CD particolarmente utili se combinati con un flusso Git avanzato:
- Se hai bisogno di un ambiente di sviluppo, configura CI per costruirlo, testarlo e distribuirlo ad ogni commit nel ramo di sviluppo. Adatta anche qui i test E2E, se ce l'hai e se ha senso nel tuo caso.
- Configura CI per creare, testare e distribuire nell'ambiente di staging su ogni commit nel ramo principale. Anche i test E2E sono piuttosto vantaggiosi a questo punto.
- Può sembrare ridondante utilizzare i test E2E in entrambi i casi, ma ricorda che gli hotfix non si verificheranno in fase di sviluppo. L'attivazione di E2E sui commit su main testerà gli hotfix e le modifiche quotidiane prima che escano, ma anche l'attivazione sui commit da sviluppare rileverà i bug prima.
- Configura CI in modo da consentire al tuo team di distribuire build dall'ambiente principale all'ambiente di produzione su richiesta manuale.
Tali modelli sono relativamente semplici, ma forniscono potenti macchinari per supportare le operazioni di sviluppo quotidiane.
Il modello di flusso Git avanzato: miglioramenti e possibili limitazioni
Il flusso Git avanzato non è per tutti. Sfrutta la controversa tattica della forza che spinge il ramo principale, quindi i puristi potrebbero risentirsi. Da un punto di vista pratico, però, non c'è niente di sbagliato in questo.
Come accennato, gli hotfix sono più impegnativi durante un rilascio ma sono comunque possibili. Con un'adeguata attenzione al QA, alla copertura dei test, ecc. Ciò non dovrebbe accadere troppo spesso, quindi dal mio punto di vista è un valido compromesso per i vantaggi complessivi del flusso Git migliorato rispetto al flusso Git classico. Sarei molto interessato a sapere come funziona il flusso Git avanzato in team più grandi e con progetti più complessi, dove gli hotfix potrebbero essere un evento più frequente.
La mia esperienza positiva con il modello di flusso Git potenziato ruota anche principalmente attorno a progetti commerciali closed-source. Potrebbe essere problematico per un progetto open source in cui le richieste pull sono spesso basate su una vecchia derivazione di rilascio dell'albero dei sorgenti. Non ci sono ostacoli tecnici per risolverlo: potrebbe semplicemente richiedere uno sforzo maggiore del previsto. Accolgo con favore il feedback di lettori con molta esperienza nello spazio open source per quanto riguarda l'applicabilità del flusso Git avanzato in questi casi.
Un ringraziamento speciale al collega di Toptal Antoine Pham per il suo ruolo chiave nello sviluppo dell'idea alla base del flusso Git migliorato.
Ulteriori letture sul blog di Toptal Engineering:
- Sviluppo basato su trunk e Git Flow
- Flussi di lavoro Git per professionisti: una buona guida Git
In qualità di Microsoft Gold Partner, Toptal è la tua rete d'élite di esperti Microsoft. Crea team ad alte prestazioni con gli esperti di cui hai bisogno, ovunque ed esattamente quando ne hai bisogno!