GIT · PROCESSI · TOOLING · FIRMWARE · CI/CD

Il Workflow Git Che Funziona Davvero per Team Firmware

2026-07-05 · Davide Carrese

Ho passato sei mesi su un progetto firmware in cui il workflow Git era il singolo più grande killer di produttività. Non il compilatore, non il debugger, non l'hardware — il processo di version control. I merge richiedevano ore. I file binari gonfiavano il repository. La CI impiegava quarantacinque minuti a ogni push. Avevamo quattro branch long-lived divergenti al punto che un merge pulito era statisticamente improbabile.

Da allora ho lavorato su oltre una dozzina di progetti firmware con team di dimensioni variabili, famiglie di MCU e toolchain diverse. I workflow Git che vedo più spesso nel mondo embedded o sono copiati dal web development (GitFlow con release branch long-lived che non hanno senso per firmware) o completamente ad-hoc ("pusha su main quando compila"). Nessuno dei due funziona bene nella pratica.

Ecco il workflow a cui sono arrivato dopo anni di tentativi ed errori. È costruito per i vincoli specifici del firmware: asset binari, dipendenze hardware, cicli CI lunghi, e la necessità di riprodurre una build esatta per certificazione o debug sul campo.

Il Modello di Branch: Trunk-Based con Feature Branch Brevissimi

Il cuore del workflow è un modello trunk-based semplificato con un singolo branch long-lived (main) e feature branch molto brevi. Niente develop, niente release, niente hotfix — tutti questi creano overhead di merge senza benefici significativi per progetti firmware.

main ──●─────●─────────●──────────●────────●──
         \   /         /          /        /
          f1●──●      f2●────────●        /
               \            /            /
                f3●─────────●───────────●

Ogni feature branch vive al massimo due o tre giorni. Se una feature richiede più tempo, viene scomposta in unità più piccole. Questa è la regola singola più impattante: nessun branch vive più di 48 ore. I branch brevi si mergiano pulitamente, si revisionano velocemente, e mantengono il modello mentale del codice accurato per tutto il team.

La regola che impongo in ogni team in cui entro: se il tuo branch ha più di tre giorni, lo rebasi su main, lo schiacci in un singolo commit, e lo pushi per review entro un'ora. Nessuna eccezione.

Gestione dei File Binari: Git LFS con una Politica Rigorosa

I repository firmware sono eccezionalmente inclini a una cattiva gestione dei file binari. File hex, librerie compilate, modelli CAD, datasheet PDF e immagini di bootloader finiscono nel repo e trasformano ogni clone e fetch in un'operazione lenta e dolorosa. Ho visto un repo firmware crescere fino a 4.7 GB in meno di un anno perché qualcuno aveva committato pacchetti STM32Cube compilati.

Ecco la politica che uso:

Aggiungo anche un job CI che fallisce se un commit introduce un file binario oltre 1 MB non tracciato da LFS. Questo previene il problema "qualcuno ha committato un PDF per sbaglio" che ogni team firmware incontra almeno una volta.

Convenzione dei Commit: Un Singolo Cambiamento per Commit

I team firmware tendono a committare tutto insieme — "aggiunta feature X, fixato bug Y, pulita formattazione, aggiornato linker script." Questo rende il bisecting quasi inutile e la code review estenuante.

La convenzione che spingo è semplice: un cambiamento logico per commit. Se tocchi un file driver e un linker script nello stesso commit, devono far parte dello stesso cambiamento logico. Se non è così, dividi il commit.

Il formato del messaggio di commit che uso:

componente: breve descrizione della modifica

Spiegazione più lunga se necessario — che problema risolve,
perché questo approccio è stato scelto rispetto ad alternative.

Fixes: #ID_ISSUE (se applicabile)
See also: SHA_DEL_COMMIT_CORRELATO (se applicabile)

Esempi concreti da un recente progetto STM32U5:

adc: aggiunta configurazione oversampling per serie U5

L'STM32U5 supporta l'oversampling hardware fino a 256x.
Questo commit espone il rapporto e lo shift di oversampling
tramite LL_ADC_Init. La dimensione del buffer DMA viene
aggiustata automaticamente quando l'oversampling è attivo.

See also: a3f8e21

linker: aumento SRAM2 a 64 KB per U5A5ZJ

Il linker script predefinito allocava solo 32 KB a SRAM2,
causando esaurimento heap durante l'avvio di FreeRTOS su
schede con configurazioni da 2 MB di flash.

Pipeline CI: Trigger Intelligenti, Non Build-Tutto

Nel web development, la CI gira in meno di un minuto. Nel firmware, una build completa di un progetto multi-target può richiedere quindici-trenta minuti. Eseguire la suite completa a ogni push è dispendioso e insegna al team a ignorare i fallimenti della CI.

Strutturo la pipeline CI in stadi con trigger crescenti:

Stage 1 — Analisi statica (sempre, ~2 minuti)
  ├── cppcheck sui file modificati
  ├── clang-format diff sui file modificati
  └── controllo file binari >1 MB senza LFS

Stage 2 — Compila target modificati (push su feature branch, ~5-10 min)
  ├── build solo dei target affetti dai file sorgente modificati
  └── usando uno script di analisi dipendenze

Stage 3 — Build completa + test (merge su main, ~20-30 min)
  ├── build di tutti i target
  ├── test unitari su host (Ceedling/CMock)
  └── test di integrazione su hardware se disponibile

Il trucco: lo Stage 2 usa un semplice script Python che mappa i file modificati ai target di build. Se hai cambiato solo un driver UART per STM32G4, la CI non ricostruisce il target STM32U5. Questo taglia il tempo medio di CI sui feature branch da venticinque minuti a meno di otto.

Tag di Rilascio: Tag Riproducibili

I rilasci firmware non sono come i deployments web. Non puoi fare rollback a un'immagine container — hai bisogno della fonte esatta + toolchain + configurazione che ha prodotto il binario in esecuzione sul campo. Questo è critico per applicazioni medicali, automotive e industriali dove la tracciabilità di certificazione è obbligatoria.

La mia convenzione per i tag:

v1.2.3+build20260705-gcc12.3-cm4
  │      │                 │         │
  │      │                 │         └─ architettura target
  │      │                 └─────────── versione toolchain
  │      └───────────────────────────── data build
  └──────────────────────────────────── versione semantica

Ogni tag di release è accompagnato da un file release-notes-v1.2.3.md che documenta:

Taggo anche gli artifact di build in Git LFS con lo stesso tag di release: git fetch --tags && git checkout v1.2.3+build20260705-gcc12.3-cm4 -- artifacts/.

Checklist pratica

Il framework che uso

Ho codificato questo workflow in uno script firmware-git-init che eseguo all'inizio di ogni progetto. Crea .gitattributes, .gitignore, lo scheletro della configurazione CI, e un CONTRIBUTING.md che spiega le convenzioni di branching e commit a chiunque si unisca al team. Ci vogliono dieci minuti per impostarlo; il tempo risparmiato solo in conflitti di merge prevenuti si ripaga nella prima settimana.

Cosa ha funzionato per me

La regola del branch breve è quella a cui i team resistono di più e che offre il maggior valore. Ogni volta che mi sono unito a un team che diceva "i nostri branch durano due settimane perché la feature è complessa", la ragione vera era che la feature non era scomposta in unità indipendenti. Una volta imposto il limite di 48 ore, il team ha imparato naturalmente a suddividere meglio il lavoro — e la qualità del codice è migliorata perché le review erano più piccole, più veloci e più mirate.

La politica sui binari è al secondo posto. Rimuovere i blob vendor dal repository ha ridotto il tempo di clone su un progetto da ventidue minuti a meno di due. Il team è passato dall'evitare git clone all'eseguirlo regolarmente per il setup CI, migliorando la riproducibilità delle build su tutta la linea.

Il firmware embedded ha vincoli unici, ma Git non è uno di questi. I workflow progettati per team web e mobile funzionano anche nel firmware — devi solo tenere conto dei cicli CI più lunghi e dei file binari. I principi sono gli stessi: branch brevi, commit chiari, gate di qualità automatici e rilasci riproducibili.

📬 Commenti / discussione

Scrivimi a: comments@carrese.eu — includi l'URL dell'articolo così posso seguire. Per correzioni o domande più approfondite, di solito rispondo entro 48 ore.