Messaggi di commit Git: come scriverli bene, maiuscole incluse

Un buon messaggio di commit Git ha una riga d'oggetto (il subject) breve, di circa 50 caratteri, scritta all'imperativo inglese, “Add” e non “Added”, e senza punto finale. Poi una riga vuota, poi un corpo a capo a 72 caratteri che spiega perché è stata fatta la modifica. Per le maiuscole, scegli una convenzione e rispettala: con un prefisso come feat: o docs:, la parola che segue è di solito in minuscolo; senza prefisso, il consiglio classico è di scrivere la prima parola con la maiuscola.
Niente di tutto questo è imposto da Git stesso, ed è per questo che le cronologie si sfilacciano. Qui sotto trovi le regole, da dove viene ciascuna e come farle controllare ai tuoi strumenti, così non devi ricordartele.
Anatomia di un buon messaggio di commit
fix(editor): keep cursor position after paste
Pasting into an empty textarea moved the cursor to the start, because
the change handler replaced the value before restoring the selection.
Restore the selection after updating the value instead.
Fixes #142Tre parti: un oggetto che dice che cosa fa il commit, un corpo che dice perché, e dei footer facoltativi per riferimenti e metadati. La documentazione di Git consiglia di cominciare con “a single short (no more than 50 characters) line summarizing the change, followed by a blank line and then a more thorough description” (una sola riga breve, di non più di 50 caratteri, che riassume la modifica, seguita da una riga vuota e poi da una descrizione più approfondita). Tutto ciò che precede la prima riga vuota è trattato come il titolo del commit, ed è ciò che mostrano git log --oneline, git shortlog e git format-patch.
L'oggetto: breve, all'imperativo, senza punto finale
Cinquanta caratteri sono un limite morbido. La guida SubmittingPatches del progetto Git lo definisce proprio così e chiede ai contributori di “skip the full stop” (omettere il punto finale). Se non riesci a scendere sotto i 50, spesso è il segno che il commit fa due cose.
La stessa guida chiede di “describe your changes in imperative mood” (descrivere le modifiche al modo imperativo), come se dessi ordini al codice: “make xyzzy do frotz”, non “makes xyzzy do frotz” né “changed xyzzy to do frotz”. Un test comodo, tratto dalle sette regole di Chris Beams sui messaggi di commit: un buon oggetto completa la frase “If applied, this commit will …” (se applicato, questo commit …).
- “If applied, this commit will add CSV export” funziona.
- “If applied, this commit will added CSV export” no.
- “If applied, this commit will bug fixes” nemmeno, e non dice nulla al lettore.
L'imperativo corrisponde anche ai messaggi che Git scrive per te, come Merge branch 'main' e Revert "…", quindi i tuoi commit si leggono nel log allo stesso modo.
Maiuscole nell'oggetto: i due schieramenti
È qui che i consigli sono davvero in conflitto, e la risposta giusta dipende dal fatto che i tuoi oggetti comincino o no con un prefisso.
| Convenzione | Esempio | Chi la consiglia |
|---|---|---|
| Nessun prefisso, iniziale maiuscola | Add CSV export | La nota di Tim Pope del 2008 sui messaggi di commit; le sette regole di Chris Beams |
| Prefisso di area, minuscolo dopo di esso | doc: clarify sign-off rules | SubmittingPatches del progetto Git |
| Prefisso di tipo, descrizione in minuscolo | feat: add CSV export | Le linee guida sui commit di Angular; la configurazione conventional di commitlint |
La versione del progetto Git ha un dettaglio da copiare. Solo la prima parola dopo il prefisso perde la maiuscola, e solo perché è la prima parola: le parole che hanno sempre la maiuscola la mantengono. Il suo stesso esempio è refs: HEAD is also treated as a ref, perché HEAD si scrive sempre in maiuscolo. La stessa logica dà fix: handle iOS paste e docs: update README badges.
La nostra posizione, per i messaggi in inglese: con un prefisso, la prima parola in minuscolo, a meno che non sia un nome o una sigla. Senza prefisso, in maiuscolo. L'unico vero errore è mescolare gli stili, perché git log --oneline trasforma i tuoi oggetti in un elenco, e un elenco che cambia stile ogni poche righe dà un'impressione di trascuratezza. Lo stesso ragionamento applicato alla documentazione lo trovi nella nostra guida per scrivere una documentazione coerente.
Cosa dice davvero la specifica Conventional Commits
La specifica Conventional Commits 1.0.0 definisce questa struttura:
<type>[optional scope]: <description>
[optional body]
[optional footer(s)]Le regole principali, in breve:
feat(una nuova funzionalità) efix(la correzione di un bug) sono i due tipi che la specifica definisce, e corrispondono alle release MINOR e PATCH del Semantic Versioning, mentre le modifiche non retrocompatibili (breaking change) corrispondono a MAJOR. Altri tipi sono ammessi; la specifica citabuild,chore,ci,docs,style,refactor,perfetest, come raccomandato dalla configurazione conventional di commitlint, che si basa sulla convenzione di Angular.- Una modifica non retrocompatibile si segnala con
!prima dei due punti (feat!: drop Node 16 support) o con un footerBREAKING CHANGE:, e quel footer deve essere in maiuscolo. - Gli strumenti che leggono il formato non devono trattarlo come sensibile alle maiuscole, tranne che per
BREAKING CHANGE. Quanto ai tipi, in minuscolo o in maiuscolo, le FAQ della specifica dicono che si può usare qualsiasi grafia, “but it's best to be consistent” (ma è meglio essere coerenti).
La specifica non fissa una lunghezza per l'oggetto né richiede il modo imperativo. Quelli vengono dalla convenzione di Git.
Dove commitlint è più severo della specifica
La configurazione @commitlint/config-conventional di commitlint è il modo abituale per far rispettare il formato, e le sue impostazioni predefinite vanno oltre la specifica. Abbiamo passato degli oggetti di esempio nella versione 21:
| Oggetto | Esito |
|---|---|
feat: add CSV export | Passa |
feat: Add CSV export | Non passa: l'oggetto non può essere in stile Frase (sentence case) |
Feat: add CSV export | Non passa: il tipo deve essere in minuscolo |
fix: handle empty textarea. | Non passa: il punto finale non è ammesso |
fix: README typo | Non passa: conta anch'esso come stile Frase |
fix: typo in README | Passa |
La riga con README è quella da tenere d'occhio. Un oggetto che comincia con una sigla o con il nome di una classe, come fix: README typo o feat: URLSearchParams support, viene respinto, anche se il progetto Git accetterebbe l'equivalente. Riformula in modo che la sigla non sia la prima parola, oppure allenta la regola subject-case nella tua configurazione. Il limite predefinito per l'intestazione è di 100 caratteri, quindi commitlint non ti vincola nemmeno ai 50.
Il corpo: a capo a 72 e spiega perché
Git non va mai a capo al posto tuo. La spiegazione di Tim Pope del 2008 vale ancora: git log rientra i messaggi di quattro spazi, quindi su un terminale da 80 colonne, altri quattro per simmetria sulla destra lasciano 72 colonne, e il testo spezzato resta leggibile anche quando i commit vengono inviati per email con git format-patch. Il nostro strumento Ritorna a capo a N caratteri manda a capo un paragrafo a 72 caratteri sui confini delle parole, se il tuo editor non lo fa.
Usa il corpo per ciò che il diff non può mostrare: il problema, perché proprio questa correzione, e tutto ciò che hai provato senza successo. Dalla riga di comando, ogni -m diventa un paragrafo a sé, quindi git commit -m "fix: keep cursor after paste" -m "Pasting into…" produce un oggetto, una riga vuota e un corpo.
Lascia che siano gli strumenti a controllare
- VS Code: imposta
"git.inputValidation": true. La casella del commit avvisa quando l'oggetto supera i 50 caratteri (git.inputValidationSubjectLength) o un'altra riga supera i 72 (git.inputValidationLength). - Vim: le sue impostazioni incluse per i messaggi di commit mandano a capo il corpo a 72 (
textwidth=72) e smettono di evidenziare l'oggetto dopo 50 caratteri. - Un template di commit:
git config commit.template ~/.gitmessageapre l'editor di ogni commit con i tuoi promemoria già scritti. - Un hook commit-msg per controlli senza dipendenze. Salva questo come
.git/hooks/commit-msge rendilo eseguibile:
#!/bin/sh
# Checks the subject line of every commit message.
subject=$(head -n 1 "$1")
length=${#subject} # counts bytes in some shells; fine for ASCII subjects
if [ "$length" -gt 72 ]; then
echo "commit-msg: subject is $length characters (limit 72, aim for 50)" >&2
exit 1
fi
if [ "$length" -gt 50 ]; then
echo "commit-msg: note, subject is $length characters (aim for 50)" >&2
fi
case "$subject" in
*.) echo "commit-msg: drop the full stop at the end of the subject" >&2
exit 1 ;;
esac
if [ -n "$(sed -n 2p "$1")" ]; then
echo "commit-msg: leave line 2 blank, between subject and body" >&2
exit 1
fiAvvisa oltre i 50 caratteri, rifiuta gli oggetti oltre i 72, il punto finale e la riga vuota mancante, e git commit --no-verify lo salta quando proprio serve. Per un conteggio veloce prima di fare il commit, incolla l'oggetto nel nostro Contatore di caratteri.
Ricapitolando, la checklist: oggetto all'imperativo, circa 50 caratteri, nessun punto finale, un'unica regola di maiuscole per tutto il repository, una riga vuota, un corpo a capo a 72 che spiega perché, e un hook o un linter perché le regole sopravvivano a una settimana frenetica.


