Git au quotidien
Un historique qui raconte quelque chose, et le calme de savoir qu'on peut presque toujours revenir en arrière.
Beaucoup de développeurs utilisent Git avec cinq commandes apprises par cœur, et une petite angoisse dès que quelque chose sort de l'ordinaire. C'est normal : Git est puissant, et ses messages ne sont pas toujours rassurants.
Cette fiche vise deux choses. D'abord, un historique utile : qui permet de comprendre, d'annuler et de retrouver un bug. Ensuite, la confiance : une fois qu'un travail est commité, il est très difficile de le perdre vraiment.
Les commandes Git sont des illustrations : elles n'ont pas été exécutées pour produire cette page, et les identifiants de commit (8b14f72…) sont inventés. Essaie-les d'abord dans un dépôt d'entraînement, comme celui de l'exercice.
1.Des commits à sujet unique
Voici deux historiques du même travail :
# Illustration : un historique tel que l'affiche « git log --oneline »
# ✗ Avant : impossible de savoir ce qui a changé, ni de revenir sur une seule modification
e41b2c9 wip
9a07f13 fix
51cc8d0 encore un fix
2f6e4ab modifs panier + typo + maj symfony
# ✓ Après : un commit = un changement, lisible et annulable séparément
c3d91e0 Offre les frais de port à partir de 50 €
8b14f72 Corrige le total du panier quand une remise s'applique
71ad5e9 Met à jour Symfony en 7.3.4
04f2b1c Corrige une faute dans le libellé du panierDans le premier, le commit 2f6e4ab mélange une correction, une faute de frappe et une mise à jour de Symfony. Si la mise à jour casse la production, impossible de l'annuler sans annuler la correction avec. Un commit à sujet unique (on dit aussi atomique) contient un seul changement logique, complet, et le projet fonctionne après lui.
- Il s'annule seul :
git revert 71ad5e9défait la mise à jour de Symfony, et rien d'autre. - Il se relit vite en revue de code : un sujet à la fois.
- Il rend
git bisectefficace (section 6) : le commit fautif désigne un seul changement.
Pour y arriver quand on a modifié plusieurs choses dans le même fichier, git add -p (« par morceaux ») propose chaque modification une par une :
# Illustration : séparer deux changements faits dans le même fichier
git status # 1. Qu'est-ce qui a changé ?
git diff # 2. Relire chaque modification
git add -p src/Cart.php # 3. Choisir morceau par morceau (y = oui, n = non, s = découper)
git diff --staged # 4. Relire ce qui partira dans le commit
git commit # 5. L'éditeur s'ouvre pour écrire le message
git add src/Cart.php # 6. Le reste du fichier, pour le commit suivant
git commitgit add . ajoute tout, y compris un .env.local avec un mot de passe de base de données. Un secret commité puis supprimé dans le commit suivant reste dans l'historique, et quiconque clone le dépôt peut le lire.
- Relis toujours
git diff --stagedavant de commiter. - Garde les fichiers sensibles dans le
.gitignore. Symfony en fournit un qui exclut déjà les bons fichiers :
# Extrait du .gitignore d'un projet Symfony
/.env.local
/.env.local.php
/.env.*.local
/config/secrets/prod/prod.decrypt.private.php
/var/
/vendor/Si un secret est parti malgré tout, considère-le comme compromis : change-le tout de suite (nouveau mot de passe, nouvelle clé), avant même de nettoyer l'historique. Pour les secrets de production, Symfony propose un coffre chiffré : bin/console secrets:set.
Sur une branche personnelle de travail, des commits « wip » réguliers sont une bonne sauvegarde. Ce qui compte, c'est l'historique que tu partages : range-le avant d'ouvrir la pull request (section 5), ou laisse l'outil fusionner la branche en un seul commit si l'équipe travaille ainsi (squash merge).
2.Des messages qui expliquent
Le diff montre ce qui a changé. Le message doit dire pourquoi, comme un bon commentaire (fiche 2).
Offre les frais de port à partir de 50 €
Le service marketing lance l'offre le 1er décembre (ticket SHOP-412).
Le seuil est comparé au total APRÈS remise, comme le demande
la règle validée avec la comptabilité.
Le montant du seuil est une constante pour pouvoir le changer
sans toucher au calcul.- Une première ligne courte (une cinquantaine de caractères), qui résume le changement. C'est elle qu'affichent
git log --onelineet les outils de revue. - Une ligne vide, puis un corps qui explique la raison, le contexte, le ticket, les choix faits.
- Une convention d'équipe : langue, temps du verbe, préfixes. Certaines équipes utilisent les Conventional Commits (
feat:,fix:…). Le plus important est que tout le monde fasse pareil.
3.Une branche par tâche
Une branche est une ligne de travail séparée. Chaque tâche a la sienne, ce qui permet de travailler sur plusieurs choses, de les faire relire séparément, et de les livrer dans n'importe quel ordre.
# Illustration : une branche par tâche
git switch main # partir de la branche principale…
git pull # …à jour
git switch -c feature/free-shipping # créer la branche et s'y placer
# … travail, commits …
git push -u origin feature/free-shipping # publier la branche, puis ouvrir la pull request
# Une urgence arrive alors que le travail n'est pas prêt à être commité
git stash push -m "frais de port en cours" # mettre de côté
git switch main
# … correction urgente sur sa propre branche …
git switch feature/free-shipping
git stash pop # reprendre là où on en était- Un nom qui dit la tâche :
feature/free-shipping,fix/cart-total, ou le numéro du ticket. - Une vie courte : quelques jours plutôt que quelques semaines. Plus une branche vit longtemps, plus elle s'éloigne de
mainet plus les conflits sont douloureux. git stashmet de côté un travail pas encore prêt à être commité. C'est pratique pour une interruption courte. Pour plus long, un commit « wip » sur la branche est plus sûr : une remise oubliée finit souvent par être perdue.
4.Se mettre à jour : merge ou rebase
Pendant que tu travailles sur ta branche, main avance. Pour intégrer ces nouveautés, deux façons existent :
mergeajoute un commit de fusion. L'historique est vrai (on voit que les deux lignes ont existé en parallèle), mais il se remplit de croisements.rebaserejoue tes commits par-dessus la version à jour. L'historique est linéaire, plus facile à lire et àbisect. Mais tes commits sont recréés avec de nouveaux identifiants (D',E').
# Illustration : remettre sa branche à jour par-dessus main
git switch feature/free-shipping
git fetch origin # récupérer les nouveautés, sans rien modifier chez soi
git rebase origin/main # rejouer mes commits par-dessus la version à jour
# En cas de conflit, Git s'arrête sur le commit concerné :
git status # 1. voir les fichiers en conflit
# 2. ouvrir chaque fichier, garder la bonne version entre <<<<<<< et >>>>>>>
git add src/Cart.php # 3. marquer le conflit comme résolu
git rebase --continue # 4. passer au commit suivant
# ou, si ça tourne mal :
git rebase --abort # tout annuler, retour à l'état d'avant le rebase
# La branche avait déjà été poussée : son historique a changé, il faut forcer
git push --force-with-lease # refuse si la branche distante a reçu des commits non récupérésNe rebase jamais des commits que d'autres personnes ont déjà récupérés, en particulier sur main. Leurs copies ne correspondraient plus aux tiennes, et forcer la publication peut écraser leur travail. Sur ta propre branche, c'est sans danger. Pour la republier, utilise --force-with-lease plutôt que --force : il refuse de publier si la branche distante contient des commits que tu n'as pas encore récupérés avec git fetch.
C'est une convention d'équipe, pas une question de bien ou de mal. Une pratique courante : rebase pour mettre à jour sa propre branche, et l'outil de pull request pour fusionner dans main. Si tu débutes avec le rebase et qu'une branche est partagée avec un collègue, préfère merge.
5.Ranger avant de partager
Tu as découvert un oubli qui appartient à un commit précédent de ta branche. Plutôt qu'un commit « fix » de plus, Git sait fusionner une correction dans le commit d'origine :
# Illustration : corriger un commit précédent avant de partager sa branche
git log --oneline origin/main..HEAD
# c3d91e0 Offre les frais de port à partir de 50 €
# 8b14f72 Corrige le total du panier quand une remise s'applique
# On découvre un oubli qui appartient au commit 8b14f72
git add src/Cart.php
git commit --fixup 8b14f72 # crée un commit « fixup! Corrige le total… »
# Avant d'ouvrir la pull request, on range tout
git rebase -i --autosquash origin/main
# L'éditeur montre la liste déjà triée : le fixup est placé sous 8b14f72.
# On enregistre et on ferme : les deux commits n'en font plus qu'un.git commit --amendmodifie le dernier commit (message ou contenu). C'est le cas le plus simple.git rebase -i(interactif) ouvre la liste des commits de la branche. Chaque ligne commence par une action :pick(garder),reword(changer le message),fixup(fusionner dans le commit du dessus),drop(supprimer). Tu peux aussi changer l'ordre des lignes.
Ces deux commandes réécrivent l'historique : la règle d'or de la section 4 s'applique. Avant de partager, fais-en autant que tu veux. Une fois sur main, ne touche plus à l'historique : pour annuler un commit publié, git revert crée un nouveau commit qui fait l'inverse.
6.Trouver le commit fautif avec bisect
Le calcul des frais de port marchait il y a trois semaines. Depuis, 200 commits. Lequel l'a cassé ? git bisect applique la recherche dichotomique de la fiche 1 à l'historique : il se place au milieu, tu lui dis si c'est bon ou cassé, et il élimine la moitié restante. Pour 200 commits, il faut environ 8 étapes.
# Illustration : trouver le commit qui a cassé le calcul des frais de port
git bisect start
git bisect bad # la version actuelle est cassée
git bisect good v2.3.0 # cette version-là fonctionnait
# Git se place au milieu. On teste, puis on répond :
git bisect good # « ici, ça marche »
git bisect bad # « ici, c'est cassé »
# … Git coupe en deux à chaque réponse, et finit par afficher :
# 8b14f72 is the first bad commit
git bisect reset # revenir là où on était
# Mieux : laisser un test répondre à ta place (code de sortie 0 = bon, sinon mauvais)
git bisect start HEAD v2.3.0
git bisect run php bin/phpunit tests/Shipping/ShippingCalculatorTest.php
git bisect reset- Donne-lui un commit mauvais (souvent l'actuel) et un commit bon (une version ou un tag où ça marchait).
- À chaque étape, teste et réponds
goodoubad. Si un commit ne peut pas être testé (il ne compile pas), répondsgit bisect skip. - Avec
git bisect run, c'est un script qui répond : un code de sortie 0 veut dire « bon », 125 « impossible à tester », et les autres valeurs de 1 à 127 veulent dire « mauvais ». Un test PHPUnit qui reproduit le bug (fiche 3) fait parfaitement l'affaire. S'il n'existait pas encore dans les anciens commits, copie-le hors du dépôt avant de lancer la recherche. - Termine toujours par
git bisect reset, pour revenir sur ta branche.
C'est là que les commits à sujet unique (section 1) paient : bisect désigne un commit de dix lignes, pas un commit « modifs panier + typo + maj symfony ».
7.Récupérer une erreur avec reflog
Un reset de trop, une branche supprimée, un rebase raté : les commits semblent avoir disparu. En réalité, Git note dans le reflog (journal des références) chaque position de HEAD, c'est-à-dire chaque endroit où tu t'es trouvé. Un commit « perdu » est encore là, il suffit de retrouver son identifiant.
# Illustration : récupérer des commits « perdus » après une fausse manipulation
git reflog
# 4c2e8a1 HEAD@{0}: reset: moving to HEAD~3
# c3d91e0 HEAD@{1}: commit: Offre les frais de port à partir de 50 €
# 8b14f72 HEAD@{2}: commit: Corrige le total du panier quand une remise s'applique
# 1. Mettre d'abord le bon état à l'abri, sur une nouvelle branche : rien n'est écrasé
git switch -c rescue c3d91e0
# 2. Vérifier que tout est là
git log --oneline -3
# 3. Seulement ensuite, décider quoi en faire (fusionner, renommer la branche…)Le réflexe est de créer une nouvelle branche sur le bon commit, plutôt que de lancer un reset --hard : une branche ne détruit rien, alors qu'un reset --hard efface aussi les modifications non commitées.
| J'ai… | Je récupère avec… |
|---|---|
fait un reset de trop | git reflog, puis git switch -c rescue <id> |
| supprimé une branche | git reflog pour trouver son dernier commit, puis une nouvelle branche dessus |
| raté un rebase en cours | git rebase --abort |
| terminé un rebase, et le résultat est faux | git reflog pour retrouver l'état d'avant le rebase |
| abîmé un fichier, sans commit | git restore <fichier> (attention : ses modifications sont perdues) |
| publié un commit à annuler | git revert <id> |
- Il est local : il ne contient que ce qui s'est passé sur ta machine, et il ne se partage pas.
- Il est temporaire : par défaut, Git le nettoie après 90 jours, et après 30 jours pour les commits qui ne sont plus sur aucune branche.
- Il ne connaît que les commits. Une modification jamais commitée, effacée par
git restoreoureset --hard, est perdue pour de bon. D'où le conseil : commite souvent, même « wip », et range ensuite.
Pièges fréquents
| Piège | Ce qui arrive | La parade |
|---|---|---|
git add . sans relire | Un secret ou un fichier de debug part dans l'historique | git diff --staged, et un bon .gitignore |
| Supprimer un secret dans le commit suivant | Il reste lisible dans l'historique | Le changer immédiatement, il est compromis |
| Un commit « modifs diverses » | Impossible à annuler ou à relire séparément | Un commit par sujet, avec git add -p |
Rebase de main ou d'une branche partagée | Les copies des collègues ne correspondent plus | Rebase seulement sur ta propre branche |
git push --force | Écrase le travail poussé par quelqu'un d'autre | --force-with-lease |
| Une branche qui vit trois semaines | Des conflits énormes au moment de fusionner | Des branches courtes, mises à jour souvent |
reset --hard pour « réparer » | Les modifications non commitées sont effacées | Une nouvelle branche sur le bon commit |
Un stash oublié | Un travail perdu de vue pendant des semaines | Un commit « wip » sur la branche |
Checklist avant de pousser
- Je suis sur la bonne branche, et elle porte le nom de la tâche.
- J'ai relu
git diff --staged: aucun secret, aucundump(), aucun fichier inutile. - Chaque commit traite un seul sujet, et le projet fonctionne après chacun d'eux.
- Chaque message dit pourquoi, avec le ticket s'il y en a un.
- Ma branche est à jour avec
main, et les tests passent après la mise à jour. - Je n'ai réécrit que des commits jamais partagés, et je pousse avec
--force-with-leasesi besoin.
Exercice : casser, chercher, réparer
Fais cet exercice dans un dossier d'entraînement, en dehors de tes vrais projets, par exemple ~/git-entrainement. Tu n'y risques rien.
- Crée le dossier, initialise un dépôt avec
git init, et ajoute un fichierprice.phpavec une fonction de frais de port (reprends celle de la fiche 2). - Fais une dizaine de petits commits à sujet unique : un commentaire, un renommage, une constante… Dans l'un d'eux, glisse discrètement un bug (
>au lieu de>=). - Pose un tag sur le premier commit,
git tag v1, puis retrouve le commit fautif avecgit bisect. Combien d'étapes a-t-il fallu ? Est-ce cohérent avec la fiche 1 ? - Recommence avec
git bisect runet un petit script de vérification inspiré decheck.php(fiche 3), qui se termine parexit(1)en cas d'échec. - Lance
git reset --hard HEAD~3, constate que trois commits ont « disparu », puis récupère-les avecgit reflogsur une brancherescue. - Sur une nouvelle branche, fais un commit avec un oubli, corrige-le avec
git commit --fixup, puis range avecgit rebase -i --autosquash.
Note tes surprises et tes blocages : ce sont les meilleures questions pour la revue.
Conseils
- Lis ce que Git te dit. Ses messages, surtout pendant un conflit ou un rebase, indiquent presque toujours la commande suivante à taper.
- Regarde l'historique en graphe avec
git log --oneline --graph --all, ou dans l'outil Git de ton éditeur. Voir les branches aide à comprendre ce que fait chaque commande. - Avant une opération risquée, crée une branche de sauvegarde :
git branch backup. Elle ne coûte rien et tu la supprimes après. - Commite souvent, range ensuite. Un commit « wip » protège ton travail. Le rangement vient avant le partage.
- N'aie pas peur de demander. Tout le monde a déjà « perdu » un travail dans Git. Dans la grande majorité des cas, le reflog le retrouve.
Documentation officielle
- Le livre Pro Git, en français
- git add (dont l'option
-p) et git commit (dont--fixup) - git rebase et git push (dont
--force-with-lease) - git bisect
- git reflog
- Les secrets dans Symfony