Améliorez vos messages de commits avec husky et commitlint
Par Maxime Bréhin • Publié le 6 sept. 2021

La constitution d’un historique Git lisible et utile passe par la bonne rédaction des messages de commits. Mais comment savoir si un message est bien rédigé, quelle longueur de ligne est pertinente, comment hierarchiser l’information ?

De nombreuses personnes se sont posé ces questions et ont tenté de normer la structure de ces messages. Il existe donc différentes conventions. Que vous adhériez ensuite à une convention existante ou que vous décidiez de créer la vôtre, comment garantir qu’elle sera bien appliquée et respectée ?

Dites bonjour à commitlint !

Vous préférez une vidéo ?

Si vous êtes du genre à préférer regarder que lire pour apprendre, on a pensé à vous :

À quoi ça sert ?

Commitlint est, comme son nom l’indique, un linter de (message de) commit, c’est-à-dire qu’il va analyser le texte qui lui sera transmis. L’idée est donc de déclencher cette analyse à chaque commit et d’interdire la finalisation du commit si le message ne satisfait pas nos contraintes.

Par défaut commitlint cherchera à ce que vous respectiez la structure suivante, issue d’une convention globale appelée conventional commit :

<type>[contexte optionnel]: <description>

[corps optionnel]

[notes/pied de message optionnels]

Cette structure est généralement suffisante (très souple avec des règles optionnelles) et bénéficie de nombreux outillages basés sur le vocabulaire qu’elle propose (automatisation des publications de versions, génération automatique des notes de version, etc.).

On trouve quelques alternatives, comme gitmoji qui recourt aux emojis pour permettre une interprétation plus visuelle de notre historique.

Si toutefois l’emploi d’une convention existante ne satisfaisait pas vos besoins, vous pouvez créer votre propre série de règles, soit en passant par un module indépendant réutilisable, soit en intégrant à même le projet votre configuration.

Installation et configuration

Il s’agit de modules npm qu’on va associer à la phase de développement afin de les partager au sein du projet :

npm install --save-dev @commitlint/cli @commitlint/config-conventional

Pour la configuration, vous pouvez choisir d’installer le module qui répondra le mieux à vos attentes (ou de créer le vôtre) :

On crée ensuite le fichier de configuration local commitlint.config.js pour dire à commitlint quelle configuration charger :

module.exports = {
  extends: ['@commitlint/config-conventional'],
}

Et pour que tout ça s’exécute automatiquement à chaque message de commit renseigné, on demande à husky de le charger via le hook commit-msg :

npx husky add .husky/commit-msg 'npx --no-install commitlint --edit $1'

Configurer pour l’intégration continue

Il est possible que vous souhaitiez configurer commitlint ailleurs que sur les postes utilisateurs pour forcer vos contraintes pour tous les acteurs, même celles et ceux qui décideraient de contourner commitlint ou qui simplement ne l’auraient pas installé. Un cas d’exemple serait la sous-traitance d’une partie de votre projet à un prestataire. Vous n’avez donc pas la garantie ni même les moyens d’assurer le bon respect de vos règles. Cependant vous avez la main sur l’intégration continue. Reste alors à y mettre en place commitlint.

À l’usage, ça donne quoi ?

Comme on l’a dit, tout ça va s’exécuter automatiquement quand on fera nos commits ; nous serons stoppés si un message ne remplit pas nos critères :

Peut-on être aidé·e plutôt (et plus tôt) lors de l’écriture ?

Le contrôle en sortie est toujours utile, mais bénéficier d’une assistance pour rédiger le message constitue un complément appréciable. Il est fort probable que (comme moi) vous oubliiez les critères et le vocabulaire mis en place. C’est pourquoi commitlint propose l’intégration avec un assistant.

npm install --save-dev @commitlint/cz-commitlint commitizen

Cet assistant s’appelle commitizen. Pour l’associer à commitlint on doit configurer la passerelle dans le fichier package.json. On devra ensuite utiliser npx cz à la place de notre habituel git commit :

"config": {
  "commitizen": {
    "path": "@commitlint/cz-commitlint"
  }
}

Note : si vous voulez une "meilleure" intégration avec Git, vous pouvez installer commitizen en global avec npm install --global commitizen afin de pouvoir faire git cz (voire juste cz) au lieu de npx cz.

Voici ce que ça peut donner :

En bonus appréciable vous pouvez modifier les textes de l assistant. Par exemple pour les traduire :

module.exports = {
  parserPreset: 'conventional-changelog-conventionalcommits',
  rules: {
    ...
  },
  prompt: {
    messages: {
      skip: ':skip',
      max: 'pas plus de %d caractères',
      min: 'au moins %d caractères',
      emptyWarning: 'ne peut être vide',
      upperLimitWarning: 'au-dessus de la limite',
      lowerLimitWarning: 'sous la limite'
    },
    questions: {
      type: {
        description: "Choisissez le type de modification que concerne votre commit :",
        enum: {
          feat: {
            description: 'Ajout/mise à jour de fonctionnalité',
            title: 'Features',
            emoji: '✨',
          },
          fix: {
            description: 'Correction de bug',
            title: 'Bug Fixes',
            emoji: '🐛',
          },
          docs: {
            description: 'Ajout/modif. de documentation',
            title: 'Documentation',
            emoji: '📚',
          },
          style: {
            description: 'Modifs de style et de mise en forme du code (espacements, virgules, etc.)',
            title: 'Styles',
            emoji: '💎',
          },
          refactor: {
            description: 'Modif. des sources n’étant ni un correctif, ni un ajout de fonctionnalité',
            title: 'Code Refactoring',
            emoji: '📦',
          },
          perf: {
            description: 'Amélioration de la performance',
            title: 'Performance Improvements',
            emoji: '🚀',
          },
          test: {
            description: 'Ajout ou correction de tests',
            title: 'Tests',
            emoji: '🚨',
          },
          build: {
            description: 'Modif. affectant le "build" ou les dépendances externes (exemples de contextes :  webpack, broccoli, npm)',
            title: 'Builds',
            emoji: '🛠',
          },
          ci: {
            description: 'Modif. de la configuration ou des scripts liés à la CI (Travis, Circle, BrowserStack, SauceLabs, etc.)',
            title: 'Continuous Integrations',
            emoji: '⚙️',
          },
          chore: {
            description: "Autres mises à jour ne modifiant ni les sources, ni les tests",
            title: 'Chores',
            emoji: '♻️',
          },
          revert: {
            description: 'Annuler (revert) un commit précédent',
            title: 'Revert',
            emoji: '🗑',
          },
        },
      },
      scope: {
        description:
          'Quel est le contexte des modifications (composant, nom de fichier)',
      },
      subject: {
        description: 'Écrivez une description concise, à l’impératif',
      },
      body: {
        description: 'Renseignez une description plus détaillée des modifications',
      },
      isBreaking: {
        description: 'Y a-il des changements majeurs ("breaking changes") ?',
      },
      breakingBody: {
        description:
          'Un commit cassant la compatibilité ascendante ("breaking changes") nécessite un corps de message. Veuillez renseigner une description plus longue et détaillée que la première ligne du commit.',
      },
      breaking: {
        description: 'Décrivez les "breaking changes"',
      },
      isIssueAffected: {
        description: 'Cela concerne-t-il un ticket existant ?',
      },
      issuesBody: {
        description:
          'Vous devez ajouter un corps au message si ce commit ferme des tickets. Essayez de renseigner une description plus longue et détaillée que la première ligne du commit.',
      },
      issues: {
        description: 'Ajoutez une référence de ticket ("fix #123", "ref #123")',
      },
    },
  }
}

Certains éditeurs proposent également des extensions. Pour VSCode par exemple, vous serez probablement séduit·e par le lint live ou par l’assistant de saisie de commits conventionnels.

Résumons tout ça

Commitlint, associé à husky et commitizen, nous permet de qualifier et normer au mieux nos messages de commits. On gagne alors en confort grâce à l’automatisation, on uniformise et gagne en qualité grâce au respect de la convention, on favorise la lisibilité et le suivi du projet avec un historique désormais clair et efficace.

On peut par la suite chercher des solutions complémentaires pour continuer l’automatisation d’une partie de nos process.

Mais avant ça, peut-être n’avez-vous pas encore automatisé tout ce qui est source d’erreurs dans votre travail avant sa publication/son partage ? Voici nos autres articles sur le sujet qui pourraient vous servir :

Découvrez nos cours vidéo ! 🖥

Nos cours vidéo sont un complément idéal et bon marché à nos articles techniques et formations présentielles. Autour de Git, de JavaScript et d’autres sujets, retrouvez des contenus de très grande qualité à des prix abordables, spécialement conçus pour lever vos plus gros points de blocage.