Sur un nouveau dépôt GitHub, n'importe quelle personne disposant des droits d'écriture peut pousser ce qu'elle veut sur main, à tout moment, y compris un push forcé qui remplace l'historique. C'est le réglage par défaut, et beaucoup de projets le gardent pendant des années sans y penser.
Le geste qui en découle paraît anodin : finir sa fonctionnalité, revenir sur main, lancer git merge, puis git push. Trente secondes, aucun clic sur GitHub. Ce raccourci fonctionne tant que personne ne se trompe. Le jour où le merge embarque un fichier de configuration local, un test cassé ou un conflit mal résolu, l'erreur est sur la branche que tout le monde récupère et que le serveur de production déploie.
Ce qui manque dans ce flux, c'est un point d'arrêt entre « mon code est prêt » et « mon code est sur main ». La pull request crée ce point d'arrêt, et la protection de branche le rend obligatoire. Les deux se règlent en dix minutes, et la suite montre comment, avec les cas où la configuration se retourne contre toi.
Le problème : main sert de brouillon partagé
Imaginons une équipe de trois développeurs sur une application Node.js. Le premier fusionne sa branche en local, oublie de récupérer les derniers changements, résout un conflit en gardant sa version et pousse. Le deuxième a poussé un correctif dix minutes plus tôt, que ce merge vient d'écraser sans bruit. Le troisième fait un git pull le lendemain matin et hérite d'une application qui ne démarre plus, sans savoir lequel des commits de la veille est en cause.
Aucune de ces trois personnes n'a fait quelque chose d'extraordinaire. Le problème vient de la configuration, qui laisse chaque erreur individuelle atteindre directement la branche commune. Les tests existent peut-être, mais rien n'oblige à les lancer avant d'intégrer. La relecture de code est peut-être une règle d'équipe, mais rien ne l'impose techniquement.
Le cas extrême est le push forcé. Après un rebase ou un reset local, un git push --force sur main remplace l'historique distant par celui de la machine qui pousse. Les commits des autres disparaissent du dépôt partagé, et seuls leurs clones locaux en gardent une copie. Les commandes de réécriture d'historique et leurs effets sont détaillées dans l'article sur merge, rebase, reset, revert, cherry-pick et reflog.
Le principe : main ne reçoit que du code qui a passé un contrôle
Une pull request est une demande d'intégration : une branche est proposée pour fusion dans une autre, et GitHub affiche les différences, les commentaires et le résultat des vérifications automatiques avant que quiconque appuie sur le bouton de fusion. Elle transforme un geste individuel et invisible en étape visible, datée et commentable.
La protection de branche ajoute la contrainte. Une fois activée sur main, GitHub refuse tout push direct, interdit la suppression de la branche et le push forcé, et n'autorise la fusion qu'une fois les conditions remplies : une relecture approuvée, des tests passés, ou les deux. GitHub propose deux mécanismes pour cela, les anciennes règles de protection de branche et les rulesets, plus récents, qui offrent les mêmes contrôles avec quelques avantages : plusieurs règles peuvent se cumuler, elles peuvent être désactivées sans être supprimées, et toute personne ayant accès en lecture au dépôt peut les consulter.
Ce point de contrôle sert aussi de mémoire. Six mois plus tard, la pull request explique pourquoi une modification a été faite, qui l'a relue et quels tests passaient à ce moment-là. Un merge local poussé ne laisse qu'un commit de fusion sans contexte. Les différents modèles de branches qui s'appuient sur ce mécanisme, GitHub Flow, Git Flow ou trunk-based, sont comparés dans l'article sur les stratégies de branches Git sur GitHub.
Mettre en place la protection, étape par étape
1. Vérifier que ton offre GitHub le permet
Point rarement mentionné et source de beaucoup de recherches inutiles : avec GitHub Free, les rulesets et les branches protégées ne sont disponibles que sur les dépôts publics. Sur un dépôt privé, il faut un compte GitHub Pro, ou une organisation sur l'offre Team ou Enterprise. Un dépôt privé personnel sur l'offre gratuite affiche les écrans de configuration, mais les règles n'y sont pas appliquées.
2. Créer une règle pour la branche par défaut
Dans le dépôt, le chemin passe par l'onglet Settings, puis Rules et Rulesets, puis New ruleset et New branch ruleset. Les réglages qui comptent :
- Enforcement status sur Active, sinon la règle existe sans rien bloquer.
- Target branches : ajouter la branche par défaut, ce qui suit automatiquement un éventuel renommage de
main. - Restrict deletions et Block force pushes cochés.
- Require a pull request before merging, avec le nombre d'approbations voulu et, si utile, l'obligation de résoudre les conversations ouvertes avant fusion.
- Require status checks to pass, avec les vérifications automatiques à exiger (étape suivante).
La liste de contournement, la bypass list, reste vide par défaut : la règle s'applique alors à tout le monde, propriétaire du dépôt compris. C'est le comportement souhaité dans la grande majorité des cas.
3. Brancher un test qui bloque la fusion
Une relecture humaine laisse passer beaucoup de choses. Un workflow GitHub Actions qui lance les tests sur chaque pull request donne un verdict que personne ne peut ignorer.
# .github/workflows/ci.yml
name: CI
on:
pull_request:
branches: [main]
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
- run: npm ci
- run: npm test
Une fois ce workflow exécuté au moins une fois, le job tests apparaît dans la liste proposée par Require status checks to pass. Le déclencheur pull_request n'est pas un détail : un workflow lancé à la main ou par un autre événement ne compte pas comme vérification requise sur une pull request. La chaîne complète, des tests jusqu'au déploiement, est l'objet de la formation CI/CD avec GitHub Actions.
4. Changer son flux de travail local
Côté terminal, le changement tient en quelques commandes. L'outil en ligne de commande gh, édité par GitHub, évite même d'ouvrir le navigateur.
git switch main
git pull --ff-only
git switch -c feature/export-csv
# ... travail et commits ...
git push -u origin feature/export-csv
gh pr create --fill
gh pr checks --watch
gh pr merge --squash --delete-branch
Une tentative de push direct sur main est désormais refusée par le serveur, avec un message de ce type :
remote: error: GH013: Repository rule violations found for refs/heads/main.
remote: - Changes must be made through a pull request.
! [remote rejected] main -> main (push declined due to repository rule violations)
Ce refus est la protection qui fonctionne. Le réflexe consiste à créer une branche depuis l'état actuel avec git switch -c, à la pousser, puis à remettre la branche main locale au niveau du dépôt distant.
5. Le cas du développeur seul
GitHub n'autorise pas l'auteur d'une pull request à l'approuver lui-même. Une règle qui exige une approbation bloque donc définitivement un développeur seul sur son projet. La configuration adaptée garde la pull request obligatoire avec zéro approbation, et fait porter le contrôle sur les vérifications automatiques.
Mon avis tranché : même seul, cette configuration vaut le détour. La pull request oblige à relire son propre diff dans une autre interface que son éditeur, ce qui suffit à repérer un nombre surprenant de fichiers oubliés, de logs de débogage et de clés d'API commitées par erreur. Sur un portfolio, l'historique de pull requests montre aussi à un recruteur une façon de travailler, un point développé dans le premier projet d'un CV de développeur junior.
Les pièges qui rendent la protection inutile
Une liste de contournement trop généreuse
Ajouter le rôle administrateur à la bypass list « pour les urgences » revient souvent à désactiver la règle pour la personne qui fusionne le plus. Si un contournement est indispensable, mieux vaut le réserver à une situation précise et le retirer ensuite. Le raisonnement rejoint celui de l'article sur le principe de moindre privilège : chaque droit accordé en permanence finit par servir au mauvais moment.
Une vérification requise qui ne s'exécute jamais
Renommer le job tests en test, ou ajouter un filtre de chemins qui empêche le workflow de se lancer sur certaines pull requests, laisse la règle attendre une vérification qui n'arrivera pas. La pull request reste bloquée en attente, sans erreur explicite. Deux jobs portant le même nom dans deux workflows différents créent l'ambiguïté inverse, et la documentation GitHub recommande des noms de jobs uniques dans tout le dépôt.
La branche locale qui refuse de se supprimer après un squash
La fusion par squash rassemble les commits de la pull request en un seul commit nouveau sur main. Pour Git, les commits d'origine de la branche locale n'ont donc jamais été fusionnés, et git branch -d refuse de la supprimer. Une fois la pull request fusionnée et vérifiée, git branch -D est légitime. L'option Automatically delete head branches, dans les réglages généraux du dépôt, fait le ménage côté GitHub.
Protéger main et laisser les secrets sur les branches
La protection de branche contrôle ce qui entre dans main, pas ce qui est poussé sur les autres branches. Une clé d'API commitée sur une branche de fonctionnalité est publique dès le push sur un dépôt public, pull request ou non. Les conséquences de ce type de fuite sont décrites dans l'article sur les risques d'une application non sécurisée, et le secret scanning de GitHub fait partie des outils couverts par la formation Git, GitHub Actions et Cyber Resilience Act.
Approuver sans lire
Une règle qui exige une approbation ne garantit pas une relecture. Dans les équipes où la pull request devient un formulaire à valider, les approbations arrivent en quarante secondes sur des diffs de huit cents lignes. Des pull requests courtes, centrées sur un seul changement, et un fichier .github/CODEOWNERS qui désigne les personnes compétentes pour chaque partie du code, redonnent du sens à l'étape. L'option qui annule une approbation quand de nouveaux commits arrivent ferme une autre faille : l'approbation obtenue sur une version, suivie d'un push de dernière minute que personne n'a vu.
Ce que ça change dans une équipe
Le bénéfice le plus visible arrive le jour où quelque chose casse en production. Avec des pull requests systématiques, la question « qu'est-ce qui a changé hier ? » se règle en ouvrant la liste des pull requests fusionnées, chacune avec sa description, ses tests et sa relecture. Le symptôme décrit dans l'article "Ça marche sur ma machine" devient aussi plus rare, puisque chaque changement passe par une exécution des tests sur une machine neutre avant d'être intégré.
Pour les personnes en reconversion et les développeurs juniors, ce flux est aussi celui qu'ils rencontreront en entreprise dès la première semaine, souvent sans qu'on le leur explique. La formation Git et GitHub : gestion de versions pour débutants couvre les branches, les pull requests et la résolution de conflits, avant d'aller vers l'automatisation. Les bases elles-mêmes, commit, branche, push, sont reprises dans le guide Git pour débutants.
La configuration minimale prend dix minutes sur un dépôt existant : une règle sur la branche par défaut, push forcé et suppression bloqués, pull request obligatoire. Les tests requis peuvent venir la semaine suivante.