GIT: controlli pre-commit e automazione della release

Automazione delle release Git con SemVer e Conventional Commits, validazione con Husky/Commitlint e workflow ottimizzato tramite Shipmark.

Foto di Yancy Min su Unsplash

immagine dell'autore dell'articolo

Scritto da: Alberto

Una vita nell'informatica, per lavoro e per passione.
agsdix-far fa-calendar-alt
Data pubblicazione: 11 Gennaio 2026
agsdix-far fa-comments
Grafico rappresentante i controlli pre-commit e l'automazione della release
Qualità del codice – Controlli pre-commit e automazione della release – © serviziweb.padova.it

Gli standard coinvolti

L’automazione della release in un workflow GIT è resa possibile dall’adozione di alcuni standard:

Il versionamento semantico

Il Semantic Versioning (SemVer) stabilisce che ogni versione di un software deve essere nel formato:

MAJOR.MINOR.PATCH

Ciascuna voce è rappresentata da un numero con un significato preciso:

  • MAJOR → cambia quando introduci modifiche incompatibili con le versioni precedenti.
  • MINOR → cambia quando aggiungi funzionalità nuove ma compatibili.
  • PATCH → cambia quando correggi bug senza aggiungere funzionalità e senza rompere nulla.

I commit convenzionali

Il sistema Conventional Commits definisce un formato standard per i messaggi di commit:

<type>([optional scope]): <description>

[optional body]

[optional footer(s)]

Elementi principali

  • type (obbligatorio): Indica la natura del cambiamento. I più comuni:
    • scope (opzionale, compreso tra parentesi rotonde); specifica l’area del progetto interessata (es. api, ui, auth).
    • description (o subject): breve frase che spiega il cambiamento. Dovrebbe rispettare le seguenti regole (convenzione Angular):
      • all’incirca 50 caratteri, descrive cosa è stato fatto.
      • usare l’imperativo presente
      • non scrivere in maiuscolo la prima lettera
      • nessun punto (.) alla fine
    • body (opzionale, separato da una riga): dettagli aggiuntivi, motivazioni, contesto. Spiega il “perché” e i dettagli tecnici, utile per chi legge la storia dei commit.
    • footer (opzionale, separato da una riga): per note importanti, come:
      • BREAKING CHANGE: (in maiuscolo, seguito da uno spazio) per modifiche incompatibili (quando la modifica causa una incompatibilità, alcuni utilizzano il type feat!)
      • riferimenti a issue o ticket, con il seguente formato (convenzione Angular):
        Un bug risolto con la release dovrebbe essere indicato in una riga separata e il prefisso “Closes “, in questo modo: Closes #234
        Nel caso di risoluzione di più bug, questi possono essere elencati, separati da un virgola, in questo modo: Closes #123, #245, #992

    Esempio di commit con titolo, corpo e due righe di footer

    git commit -m "feat: aggiunge validazione lato client al form di login" \ 
              -m "Implementata la validazione dei campi email e password. \ 
                 Questo riduce gli errori di input e migliora l'esperienza utente." \ 
              -m "BREAKING CHANGE: modificata la struttura del payload di login" \ 
              -m "Closes #123"

    Il commit contiene i seguenti elementi strutturali, allo scopo di comunicare l’intento al consumatore della libreria:
    fix: un commit di tipo fix risolve un errore nel codice (correlato al PATCH in un versionamento Semver).
    feat: un commit di tipo feat introduce una nuova funzionalità al codice (correlato al MINOR in un versionamento Semver).
    BREAKING CHANGE: un commit che contiene in un piè di pagina BREAKING CHANGE:, o aggiunge un ! subito dopo il tipo/contesto, all’inizio delle sezioni opzionali corpo o piè di pagina, introduce una breaking API change (correlato al MAJOR in un versionamento Semver). Una BREAKING CHANGE può essere parte di un commit di qualsiasi tipo.
    Sono ammessi ulteriori tipi oltre fix: efeat:, per esempio commitlint-config-conventional (che si basa sulla convenzione Angular) raccomanda build:, chore:, ci:, docs:, style:, refactor:, perf:, test:, ed altri.
    Altri piè di pagina oltre BREAKING CHANGE: <description> possono essere utilizzati e seguono ua specifica simile a git trailer format.

    fonte: https://www.conventionalcommits.org/it/v1.0.0/

    Risorse online

    • 365git (citato nella convenzione Angular)
    • tbaggery (citato nella convenzione Angular)

    Strumenti per il controllo della correttezza del messaggio di commit

    E’ possibile controllare se il messaggio di commit rispetta gli standard prima che il commit venga eseguito. I tool coinvolti in questo meccanissmo sono Husky e Commitlint.

    Husky

    Husky è un tool per gestire Git hooks in modo semplice e configurabile. Ad esempio, permette di:

    • Eseguire script prima di un commit (pre-commit), come lanciare linting, formattazione, test veloci.
    • Eseguire script prima di un push (pre-push), come far girare test completi o build.
    • Bloccare commit/push se gli script falliscono.
    • Garantire che tutti nel team rispettino le stesse regole.
    • Integrarsi con strumenti come ESLint, Prettier, Lint-staged, Commitlint.

    Installazione di Husky

    Per installare Husky:

    npm install --save-dev husky

    Attivare Husky nel progetto con il comando:

    npx husky init

    Questo comando inizializza Husky e crea automaticamente:

    • la cartella .husky/ nella root del progetto
    • uno script pre-commit di esempio
    • lo script prepare: "husky" nel file package.json
    • inserisce hooksPath = .husky nel file .git/config, in questo modo istruisce GIT di non ricercre gli hook nella directory .git/hooks, bensì nella directory .husky.

    Creare l’ hook di Husky per Commitlint:

    echo 'npx --no -- commitlint --edit "$1"' > .husky/commit-msg
    chmod +x .husky/commit-msg

    Commitlint

    Commitlint è un tool di validazione dei messaggi di commit. Controlla che ogni commit rispetti un formato predefinito (di solito quello dei Conventional Commits).

    Installazione di Commitlint

    Per installare Commitlint e le regole standard di validazione, posizionati nella root del tuo progetto Laravel e invia il comando:

    npm install --save-dev @commitlint/{config-conventional,cli}

    E’ possibile configurare Commitlint affinché usi valori personalizzati per Type e Scope. Di seguito i miei personali elenchi di Type e Scope e la loro implementazione nel file di configurazione di Commitlint.

    Possibili Type

    Typeprevisto da convenzione Angular?previsto da config-conventional?Previsto da me?Prefisso branchDescrizione
    build(scope)NOaggiornamenti a Vite o a dipendenze che influenzano la build
    chore(scope)NOchore/scope/attività di manutenzione, cleanup
    ci(scope)NOfile yaml
    docs(scope)NOdocumentazione
    feat(scope)feat/scope/nuova funzionalità/adeguamento normativo
    fix(scope)fix/scope/correzione di errori non bloccanti
    perf(scope)perf/scope/quando la modifica rende il codice più veloce o più efficiente, senza cambiare cosa fa
    refactor(scope)ìrefactor/scope/si usa quando si modifica il codice senza cambiarne il comportamento esterno.
    revert (*)NOn.a.formato usato da git quando un commit annulla esplicitamente un commit precedente; il subject deve coincidere con l’header del reverted commit 
    style(scope)NOapplicazione di regole estetiche del linter
    test(scope)NOaggiunta o modifica di test
    (*) git reset --soft HEAD~1 sposta HEAD indietro di un commit, non va tracciato con type=revert

    I Type omessi nel mio elenco (build, ci, docs, style, test) diventano possibili Scope di “chore”.

    i commit di release di Shipmark, se abilitati, utilizzano il type chore(release).

    Possibili Scope per ciascun Type

    TypeScopeDescrizione
    choreconfigconfigurazioni, environment, setup
    build(alias: deps, ovvero dependencies) descrivere nel corpo del messaggio la libreria composer/NPM modificata
    cifile yaml di CI
    deployfile yaml di deploy, scripts
    docsdocumentazione
    style(alias: lint) modifiche estetiche del codice, senza variazione di comportamento
    testinfrastruttura di test
    releasenei commit di release di shipmark, se abilitati
    feat,
    feat!,
    fix
    ajaxgestione chiamate AJAX, API interne
    authautenticazione e autorizzazione, gestione utenti
    bkplogica di backup, restore
    dwhanalytics, elaborazione dati, metriche, datawarehousing, ETL, aggregazioni
    gdprcookie banner, gestione dei consensi, gestione dati personali, anonimizzazione, informativa privacy, retention
    i18nInternazionalizzazione
    pagespagine del sito
    repreporting: dashboard, esportazioni, grafici
    secSecurity
    trktracking: raccolta eventi, pixel, script
    uicomponenti grafici, layout, css, js, messaggi utente, assets
    perfdbottimizzazione query, creazione indici
    assetminifying, CDN
    image.avif; .webp; sprite sheet
    htmluso di async, defer, lazy load
    httpcompressione response http
    cachecache di pagina; cache Redir (persist object cache)
    refactorcomponente Laravel tra i seguenti:
    controllerscontroller MVC
    modelsEloquent models
    viewsBlade templates
    routesweb.php, api.php, routing in generale
    migrationscreazione e modifica di tabelle MySQL
    seedersinserimento dati in DB
    middlewarefiltri, autenticazione
    servicesclassi di servizio, logica applicativa
    jobsqueue jobs, processi asincroni
    commandscomandi Artisan personalizzati
    providersservice providers

    Configurazione di Commitlint

    Crea un file chiamato commitlint.config.js nella root del progetto e inserisci il testo:

    /* istruzioni per scope nullo: non dovrebbe mai essere empty, ma se lo è
     * viene segnalato come errore di gravità 1 ovvero warning */
    
    export default {
      extends: ['@commitlint/config-conventional'],
    
      rules: {
        // Tipi consentiti
        'type-enum': [
          2,
          'always',
          ['chore', 'feat', 'fix', 'perf', 'refactor', 'revert']
        ],
    
        // Scope consentiti
        'scope-enum': [
          2,
          'always',
          ['config', 'ci', 'deploy', 'deps', 'lint', 'test', 'release', 'ajax', 'auth', 'bkp', 'dwh', 'gdpr', 'i18n', 'page', 'rep', 'sec', 'trk', 'ui']
        ],
    
        // Lo scope non può essere vuoto
        'scope-empty': [1, 'never'],
    
        // Lunghezza massima del titolo
        'header-max-length': [2, 'always', 72],
    
        // Obbligo di avere un messaggio nel body se il tipo è "feat" o "fix"
        'body-empty': [1, 'never'],
    
        // Il body deve iniziare con una riga vuota dopo il titolo
        'body-leading-blank': [2, 'always'],
    
        // Il footer deve iniziare con una riga vuota
        'footer-leading-blank': [2, 'always'],
    
        // Il tipo deve essere in minuscolo
        'type-case': [2, 'always', 'lower-case'],
    
        // Lo scope deve essere in minuscolo
        'scope-case': [2, 'always', 'lower-case'],
    
        // Il titolo non deve terminare con un punto
        'header-full-stop': [2, 'never', '.'],
    
        // Il messaggio deve iniziare con un tipo valido
        'subject-case': [
          2,
          'never',
          ['sentence-case', 'start-case', 'pascal-case', 'upper-case']
        ],
    
        // Il subject non deve essere vuoto
        'subject-empty': [2, 'never']
      }
    };

    Per controllare il comportamento di commitlint, posizionati sulla root del progetto e invia il comando:

    echo "messaggio-da-verificare" | npx commitlint 

    Strumenti l’automazione della release

    L’automazione della release in Git serve a eliminare attività manuali (tagging, version bump, changelog, build, pubblicazione) e garantire rilasci coerenti, ripetibili e senza errori. I tool più diffusi oggi sono Semantic‑release, Release‑it e GoReleaser, insieme ai workflow nativi di GitHub/GitLab che orchestrano il processo. Altri tool che rientrano pienamente nelal categoeia sono Release-please, Shipmark e Ship.js.
    Ecco una tabella compatrativa degli strumenti:

    EsigenzaTool idealeStile di automazionePunti di forza
    Automazione totale, zero interventi manuali, versioning basato sui commitsemantic‑releaseFull‑auto (CI‑only)Determina versione automaticamente, genera changelog, crea tag, pubblica pacchetti. Standard de facto nel mondo JS.
    Controllo manuale + possibilità di automazionerelease‑itManuale + CIMolto flessibile, configurabile, ottimo per progetti che vogliono supervisione umana.
    Workflow basato su PR, auditabile, ideale per monoreporelease‑pleasePR‑based automationCrea Release PR, aggiorna versioni e changelog, ottimo per GitHub e monorepo.
    Rilasciare binari multipiattaforma (Go, CLI, DevOps)GoReleaserFull‑auto (CI)Build multi‑OS, checksum, archivi, Homebrew/Scoop, GitHub Releases. Standard nel mondo Go.
    Tool moderno, leggero, UX curata, zero dipendenzeShipmarkCLI interattiva + CISemplice, elegante, veloce, ottimo per team che vogliono un flusso chiaro e pulito.
    Automazione JS con PR di release, molto personalizzabileShip.jsPR‑based + npmIdeale per progetti JS che vogliono un flusso guidato e configurabile.

    Personalmente ho scelto Shipmark, che, diversamente dagli altri strumenti, esegue il commit sul main in locale e poi esegue il push su origin, unitamente ad un tag che dà origine al processo di deploy del software.

    Shipmark

    Shipmark è uno strumento CLI (Command Line Interface) pensato per automatizzare il workflow di rilascio nelle applicazioni che usano Git.

    Flusso da creazione del branch a fine CI con il merge su origin master

    • Situazione iniziale: main branch stabile
    • Si riceve una richiesta di nuova feature (ad esempio aggiungere la pagina di login)
    • Creazione del feature branch (es feat/add-login)
    • feature commit nel branch
    • Push del feature branch (git push origin feat/add-login); posso sincronizzare GitHub actions su push (facoltativo)
    • Apertura di una Pull Request (PR); posso sincronizzare GitHub actions su PR
    • CI: Code review + test + approvazione
    • merge su origin master (su GitHub, origin master ora contiene la nuova feature ed è un “release candidate”).

    Flusso normale

    Su GitHub:

    • Creazione del branch di release (es. release/1.4.0)
    • commit di release (automatico; bump versione, changelog, file aggiornati)
    • PR di release automatica
    • Merge della PR di release su origin master
    • Creazione TAG (v1.4.0)
    • CD pubblica la release (build + deploy)

    In sviluppo:

    • git pull origin main per allinearsi all’ultimo merge (PR di release)

    Flusso con shipmark

    • Lo sviluppatore esegue shipmark release
      • git checkout main
      • fetch del repo origin (git fetch origin)
      • merge del branch origin main nel branch main locale (git pull origin/main)
      • analisi commit (git describe –tags –abbrev=0)
      • determina versione (git log <ultimo-tag>..HEAD)
      • genera changelog
      • aggiorna package.json
      • crea commit di release (sul branch attuale) (git add . + git commit -m “chore(release): vX.Y.Z”)
      • crea tag (git tag -a vX.Y.Z -m “Release vX.Y.Z”)
      • mostra anteprima
      • chiede conferma prima del push
      • push automatico del branch main e del tag (git push origin main –follow-tag)
    • CD rileva il TAG e pubblica la release (build + deploy)

    Cosa distingue Shipmark dagli altri strumenti

    Shipmark:

    • non crea branch di release;
    • non apre PR (pull request) di release;
    • prepara tutto localmente;
    • chiede conferma prima di pushare;
    • fa un unico push contenente commit di release e tag di release

    Installazione di shipmark

    Se si vuole installare shipmark come dipendenza del progetto, posizionarsi sulla root del progetto ed inviare il comando:

    npm install @grazulex/shipmark --save-dev

    Oppure, se si vuole installare shipmark globalmente, aggiungere il paramentro -g il comando:

    npm install -g @grazulex/shipmark --save-dev

    Installando shipmark come dipendenza del progetto, per eseguirlo si dovrà sempre premettere npx. Ad esempio per controllare l’esito della installazione si doovrà usare il comando:

    npx shipmark --version

    Configurazione di shipmark

    Avviare la configurazione interattiva tramite il comando:

    npx shipmark init

    Bisognerà rispondere alle seguenti domande:

    ? ShipMark Setup
    ──────────────────────────────────────────────────
    
    ✔ Changelog file: CHANGELOG.md
    ✔ Tag prefix: v
    ✔ Commit message template: chore(release): {version}
    ✔ Auto push after release? No
    ✔ Sign tags with GPG? No
    ✔ Use conventional commits? Yes
    ✔ Configuration saved to .shipmarkrc.yml
    

    Al termine, verrà generato il file .shipmarkrc.yml, che io ho personalizzato così:

    changelog:
      file: CHANGELOG.md
      types:
        feat: Nuova funzionalità
        fix: Correzione bug
        chore: Manutenzione
        perf: Performance Improvements
        refactor: Miglioramento prestazioni
        revert: Annullamento commit
      includeHash: true
      includeDate: true
      includeAuthor: false
    version:
      files:
        - package.json
      tagPrefix: v
      tagMessage: Release {version}
      commitMessage: "chore(release): {version}"
    commits:
      conventional: true
      allowCustomTypes: true
      
      # incremento il numero di patch anche in caso 
      # di commit type = chore, perf o refactor
      types:
        feat:
          release: minor
        fix:
          release: patch 
        chore: 
          release: patch 
        perf:
          release: patch 
        refactor: 
          release: patch   
    git:
      push: false
      pushTags: false
      signTags: false
      signCommits: false

    In particolare, notare che i Type chore, perf e refactor generano anch’essi una nuova versione (con incremento del valore patch).

    Risorse online

    0 commenti