Commits atomiques
Un commit atomique contient exactement un changement logique. Il ne s'agit pas de propreté pour la propreté — il s'agit de trois capacités concrètes que l'équipe perd lorsque les commits sont mélangés :
- Revue : un relecteur peut garder une seule idée en tête et la juger.
- Réversion : un mauvais changement peut être annulé sans emporter les bons changements avec lui.
- Bisect :
git bisectpeut nommer le commit qui a introduit un bogue, ce qui ne fonctionne que si chaque commit est un changement et que chaque commit construit.
Cette dernière clause est la plus souvent oubliée. Atomique ne signifie pas « aussi petit que possible » — cela signifie « un changement complet ». Un commit qui sépare une fonction du site d'appel qu'elle nécessite est plus petit mais pas atomique, car il laisse l'arbre cassé.
Le workflow
1. Examiner avant de staging quoi que ce soit
Comprendre l'arbre entier avant de décider comment le diviser :
git status --porcelain # chaque chemin, y compris les non suivis
git diff # modifications non stagées
git diff --cached # tout ce qui est déjà stagé
git log --oneline -15 # où se trouve HEAD, et le style local
Résistez à git add -A et git commit -a comme premier geste. Ils sont la façon dont un arbre mélangé devient un commit mélangé, et une fois commité, le diviser est strictement plus de travail que de le diviser avant. Vérifiez également les modifications qui ne devraient pas du tout être commitées — sortie de build, fichiers .env, instructions de débogage, un fichier de configuration d'éditeur égaré.
2. Regrouper les modifications en unités logiques
Le test le plus fiable est linguistique : rédigez le sujet du commit, et s'il a besoin d'un « et » pour être exact, vous avez plus d'un commit. « Correction de la redirection de connexion et mise à jour de lodash » en fait deux. La question du relecteur est le deuxième test : devraient-ils comprendre le changement X pour juger le changement Y ? Si non, les modifications sont indépendantes et appartiennent à des commits séparés.
Séparer :
| Garder séparé | Pourquoi |
|---|---|
| Formatage / espaces vs. logique | Un reformatage enterre les trois vraies lignes dans 400 lignes de bruit. C'est la séparation la plus précieuse. |
| Renommages vs. changements de comportement | Même raison — un renommage touche tout et cache la seule ligne qui compte. |
| Mises à jour de dépendances vs. code | Chacune a besoin de sa propre histoire de réversion. |
| Corrections de bogues non liées | Réversions indépendantes, bisects indépendants. |
| Refactorisation vs. la fonctionnalité qui l'utilise | Permet à un relecteur de confirmer que la refactorisation préserve le comportement. |
Garder ensemble :
| Garder dans un seul commit | Pourquoi |
|---|---|
| Un changement et ses tests | Ils forment une unité logique ; des tests pour du code non livré, ou du code sans tests, c'est chaque moitié d'un changement. |
| Un renommage et tous ses sites d'appel | La séparation laisse l'arbre cassé — le contraire d'atomique. |
| Un changement de signature et ses appelants | Idem. |
| Un changement et la documentation/changelog qui le décrit | Ils décrivent la même décision et devraient être réversés ensemble. |
3. Ordonner les commits pour que chacun construise
L'ordre compte car chaque commit doit laisser l'arbre fonctionnel. Placez les fondations avant les choses qui les utilisent : ajoutez l'assistant avant le code qui l'appelle, faites la refactorisation avant la fonctionnalité construite dessus, déposez le schéma avant la requête. Si deux groupes sont mutuellement dépendants, c'est le signe qu'ils ne forment en réalité qu'un seul commit.
4. Stager chaque groupe
Utilisez la technique la moins invasive qui fonctionne. Ne montez en puissance que lorsque la plus simple ne peut pas exprimer la séparation :
Niveau 1 — stager des fichiers entiers. Cela couvre la grande majorité des séparations réelles, car les changements logiques vivent généralement dans des fichiers différents :
git add src/auth.py tests/test_auth.py
git commit -m "..."
Niveau 2 — mettre de côté des fichiers entiers lorsque quelque chose dans l'arbre pourrait autrement être facilement commité par accident :
git stash push -- requirements.txt # mettre de côté
# ... commiter le reste ...
git stash pop # le ramener
Niveau 3 — diviser les modifications à l'intérieur d'un seul fichier. Nécessaire seulement quand un fichier contient vraiment deux changements logiques. La mécanique est délicate et facile à mal faire subtilement, donc lisez references/splitting-one-file.md avant d'essayer.
git add -pne fonctionne pas dans les environnements non interactifs, et échoue silencieusement. Il affiche la première section, atteint EOF sur stdin, ne stage rien, et se termine avec 0. Un code de sortie réussi signifie ici que rien ne s'est passé. La même chose s'applique àgit add -i,git commit -petgit checkout -p. Parce que l'échec ressemble à un succès, le dégât est généralement un commit vide ou manquant le changement prévu — utilisez donc les techniques du fichier de référence à la place, et vérifiez l'index avecgit diff --cachedavant de commiter.
5. Rédiger chaque message dans la voix du dépôt
Adaptez-vous à l'historique environnant plutôt que d'importer une convention. Un préfixe feat: dans un journal de sujets en prose simple est du bruit, et donne l'impression que l'historique vient de deux projets. Lisez d'abord le style local :
git log --no-merges --format='%s' -n 30 # style des sujets
git log --no-merges -n 10 --format='%n---%n%s%n%b' # si les corps sont utilisés
Cherchez : un préfixe type: ou type(scope): (Conventional Commits), un préfixe de ticket comme [PROJ-123], la capitalisation, l'impératif vs. le passé (« Ajouter » vs. « Ajouté »), la longueur typique du sujet, et si les corps sont normaux ou les sujets suffisent seuls. Adoptez ce que vous trouvez. Conventional Commits est un bon défaut seulement lorsque le journal l'utilise déjà, ou que le dépôt n'a pas encore d'historique et que l'utilisateur n'a pas de préférence.
Quel que soit le style local, le sujet doit dire ce qui a changé et le corps — lorsque l'historique utilise des corps — doit dire pourquoi, puisque le diff montre déjà le quoi. Si le dépôt a un fichier CONTRIBUTING.md ou un hook commit-msg, ceux-ci remplacent l'inférence à partir du journal.
Respecter une convention ne signifie jamais inventer les données qu'elle porte. Si le style local intègre une référence de ticket ou de problème — [PROJ-123], Fixes #456 — et que vous n'avez pas reçu l'identifiant réel, n'en inventez pas un et ne continuez pas la numérotation que vous voyez dans le journal. Un [PROJ-124] plausible est pire que pas de préfixe du tout : il est permanent, il donne l'impression d'une traçabilité réelle, et il pointe probablement vers un ticket non lié de quelqu'un d'autre. Omettez la référence, adaptez le reste du style, et dites dans votre réponse que l'ID manque pour que l'utilisateur puisse le fournir — le commit n'est toujours pas poussé et facile à amender. Demandez le numéro si l'utilisateur est là pour le donner.
6. Vérifier avant de rendre la main
git log --oneline -n <count> # la forme de ce que vous avez construit
git status --porcelain # ce qui reste, le cas échéant
Confirmez que chaque commit ne contient que son propre changement (git show --stat <sha>), et que rien n'a été laissé de côté. Si des modifications restent non commitées intentionnellement, dites-le explicitement — un arbre que l'utilisateur croyait propre mais qui ne l'est pas est le pire résultat ici, car il le découvrira plus tard et ne saura pas si c'était délibéré.
Garde-fous
- Façonnez comment commiter, pas si commiter. Si l'utilisateur n'a pas demandé de commit, cette compétence ne rend pas approprié d'en créer un, et elle n'implique jamais de pousser.
- Ne jetez jamais de travail pour faciliter une séparation.
git checkout -- <file>,git reset --hardetgit stash dropdétruisent le travail non commité qu'aucun commit ne contient encore — il est irrécupérable. Si une technique nécessite l'un d'eux, sauvegardez d'abord le contenu et confirmez que la sauvegarde existe avant de l'exécuter. - Ne réécrivez pas l'historique publié pour le nettoyer. Diviser des commits déjà poussés signifie un force-push qui réécrit l'historique que d'autres ont pu tirer ; demandez d'abord.
- Ne sur-divisez pas pour paraître minutieux. Trois commits qui cassent chacun la construction sont pires qu'un seul qui fonctionne. En cas de doute, préférez le regroupement qui maintient chaque commit fonctionnel.
- Si l'arbre s'avère contenir un seul changement logique, commitez-le en un seul. Tous les arbres n'ont pas besoin d'être divisés, et le dire est un excellent résultat.