Aller au contenu
Développement medium

Débloquer un push Git rejeté

12 min de lecture

Votre git push affiche rejected et non-fast-forward ? C'est le symptôme le plus courant en travail d'équipe : quelqu'un a poussé des commits pendant que vous travailliez. Ce guide vous montre comment débloquer la situation avec pull --rebase, quand utiliser --force-with-lease, et comment diagnostiquer les divergences entre votre branche locale et le remote.

Prérequis : Remotes fondamentaux et Branches distantes.

  • Comprendre pourquoi un push est rejeté (divergence de branches)
  • Résoudre le rejet avec pull --rebase puis re-push propre
  • Utiliser --force-with-lease en toute sécurité quand nécessaire
  • Diagnostiquer les divergences entre local et remote
  • Configurer pull.rebase pour éviter les merges parasites automatiques

Un push rejeté n'est jamais une panne : c'est un garde-fou. Git n'accepte un push que s'il peut faire avancer la branche distante en ligne droite, sans rien perdre. C'est ce qu'on appelle une mise à jour fast-forward : tous les commits déjà présents sur le remote restent accessibles depuis le nouveau sommet de branche. Dès que ce n'est plus vrai, le serveur refuse et affiche non-fast-forward.

Quand vous tentez un push et que le remote a avancé entre-temps :

! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to 'origin'
hint: Updates were rejected because the tip of your current branch
hint: is behind its remote counterpart.

En clair : votre branche locale est en retard par rapport au remote. Git refuse de pusher parce que cela écraserait les commits de vos collègues.

Remote: A - B - C - D (commit de votre collègue)
Local: A - B - C - E (votre commit)

Le remote a D, vous avez E. Git ne peut pas simplement ajouter E après D car il remplacerait D.

La réponse au rejet consiste à intégrer d'abord ce que le remote contient, puis à repousser. Le rebase fait cette intégration en déplaçant vos commits au sommet de la branche distante, ce qui restaure la condition fast-forward que Git exigeait. Cette opération réécrit vos commits locaux (ils changent de SHA, l'empreinte qui identifie un commit), mais ils ne sont pas encore publiés : personne d'autre ne s'appuie dessus.

Dans 90% des cas, la solution est simple :

Fenêtre de terminal
git pull --rebase

Cette commande fait deux choses :

  1. Récupère les nouveaux commits du remote (git fetch)
  2. Rejoue vos commits locaux après les commits du remote

Résultat :

Avant: A - B - C - D (remote)
\- E (local)
Après: A - B - C - D - E' (linéaire ✓)

Votre commit E est rejoué comme E' (nouveau SHA, même contenu) après le commit D de votre collègue. L'historique reste linéaire.

Ensuite, poussez normalement :

Fenêtre de terminal
git push

Un conflit survient quand vos commits et ceux du remote modifient les mêmes lignes d'un même fichier. Le rebase s'arrête alors sur le commit fautif et attend votre arbitrage : tant que vous n'avez pas terminé la séquence, le dépôt reste dans un état intermédiaire signalé par git status.

  1. Résolvez les conflits dans chaque fichier

    Fenêtre de terminal
    git status
    # Éditez les fichiers listés sous "Unmerged paths"
  2. Ajoutez les fichiers résolus

    Fenêtre de terminal
    git add fichier-resolu.py
  3. Continuez le rebase

    Fenêtre de terminal
    git rebase --continue
  4. Poussez

    Fenêtre de terminal
    git push

Pour annuler le rebase en cours et revenir à l'état d'avant :

Fenêtre de terminal
git rebase --abort

Si vous préférez un merge classique au lieu d'un rebase :

Fenêtre de terminal
git pull

Cela crée un commit de merge :

A - B - C - D --- M (merge commit)
\- E ─┘

Résultat fonctionnel identique, mais l'historique contient un point de fusion supplémentaire. Le push fonctionne ensuite normalement.

Sans réglage explicite, git pull fait un merge et fabrique un commit de fusion à chaque divergence, même minuscule. Sur un dépôt d'équipe actif, l'historique se remplit vite de ces commits qui n'apportent aucune information. Le paramètre pull.rebase change ce comportement une bonne fois pour toutes, au niveau de votre poste.

Pour ne plus avoir à taper --rebase à chaque pull :

Fenêtre de terminal
git config --global pull.rebase true

Vérification :

Fenêtre de terminal
git config --global pull.rebase
# true

Désormais, git pull fera automatiquement un rebase au lieu d'un merge.

--force-with-lease force le push mais vérifie d'abord que personne n'a poussé entre-temps. C'est un « force push sécurisé ».

Toutes ces situations ont un point commun : vous avez réécrit l'historique de commits déjà publiés. Le remote détient encore les anciens SHA, votre branche locale porte les nouveaux, et aucune des deux versions ne descend de l'autre. Un pull --rebase ne réglerait rien ici, il empilerait les deux variantes du même travail.

SituationPourquoi le push est rejetéSolution
Vous avez fait --amend sur un commit déjà pousséLe SHA a changé--force-with-lease
Vous avez fait un rebase -i sur une branche pousséeLes SHA ont changé--force-with-lease
Vous avez fait un reset sur une branche pousséeDes commits ont disparu--force-with-lease
Fenêtre de terminal
git push --force-with-lease

La sécurité de --force-with-lease repose sur votre branche de suivi distante (origin/main en local), qui mémorise l'état du remote au moment de votre dernier fetch. Git compare cette référence à ce que le serveur détient réellement et abandonne si les deux diffèrent. Conséquence pratique : un git fetch juste avant le push rafraîchit cette référence et désactive de fait la protection.

CommandeComportement
git push --forceÉcrase le remote sans vérification, dangereux
git push --force-with-leaseVérifie que le remote n'a pas changé depuis votre dernier fetch, sûr

Avant toute correction, commencez toujours par un git fetch : sans lui, votre branche de suivi distante (origin/main) reflète un état périmé et vos comparaisons portent sur des données fausses. Le fetch télécharge les nouveaux commits sans toucher à votre copie de travail, il est donc sans risque. Les trois commandes ci-dessous répondent chacune à une question différente : qui a quoi, combien, et comment les branches se sont séparées.

Quand la situation est confuse, diagnostiquez avant d'agir :

Les trois points de HEAD...origin/main demandent la différence symétrique : les commits propres à chaque côté, jamais ceux qu'ils partagent. L'option --left-right préfixe chaque ligne pour indiquer à quelle branche elle appartient.

Fenêtre de terminal
git fetch
git log --oneline --left-right HEAD...origin/main

Sortie typique :

< a1b2c3d (HEAD -> main) votre commit local
< d4e5f6a votre commit local
> f7a8b9c (origin/main) commit du remote
> 1234abc commit du remote
  • < = commits présents localement mais pas sur le remote
  • > = commits sur le remote mais pas localement

Ici les deux points signifient « ce qui est accessible à droite mais pas à gauche » : l'ordre des deux références décide donc du sens du comptage. Deux résultats non nuls confirment une divergence ; un seul côté à zéro signale un simple retard, que git pull --rebase règle sans réécrire quoi que ce soit.

Fenêtre de terminal
git rev-list --count HEAD..origin/main
# Nombre de commits que le remote a en plus
git rev-list --count origin/main..HEAD
# Nombre de commits que vous avez en plus

L'option --all est indispensable : sans elle, git log n'affiche que l'historique de la branche courante et la divergence reste invisible. Avec elle, les branches locales et les branches de suivi distantes apparaissent dans le même graphe.

Fenêtre de terminal
git log --oneline --graph --all

Cela montre clairement où les branches ont divergé.

Cas spécial : push rejeté après un rebase local

Section intitulée « Cas spécial : push rejeté après un rebase local »

Si vous avez rebasé votre feature branch sur main localement, les SHA de vos commits ont changé. Le push sera rejeté car le remote a les anciens SHA :

Fenêtre de terminal
# Vous avez fait :
git switch feature/login
git rebase main
# Le push est rejeté :
git push
# ! [rejected] feature/login -> feature/login (non-fast-forward)

Solution, c'est un cas légitime pour --force-with-lease (si vous êtes seul sur cette branche) :

Fenêtre de terminal
git push --force-with-lease

Tous les rejets ne viennent pas d'une divergence. Une branche de suivi absente, une mauvaise branche courante ou un push vers une référence distante qui n'existe pas produisent des messages voisins mais appellent des corrections différentes. Repérez d'abord le symptôme exact avant d'appliquer une commande de réécriture.

SymptômeCause probableSolution
rejected après pull --rebaseConflit non résolu pendant le rebasegit rebase --continue après résolution, ou git rebase --abort
--force-with-lease aussi rejetéQuelqu'un a poussé depuis votre dernier fetchgit fetch puis réessayez, ou faites pull --rebase
Push rejeté sur une branche que personne ne toucheVotre tracking branch est mal configuréegit push -u origin feature/login
everything up-to-date mais les commits manquentVous n'êtes pas sur la bonne brancheVérifiez git branch et git log --oneline -5
Push OK mais les commits n'apparaissent pas sur GitHubPoussé sur la mauvaise branche distantegit push origin feature/login:feature/login
  • Push rejeté = le remote a avancé, c'est normal en équipe
  • git pull --rebase résout 90% des cas en gardant un historique linéaire
  • --force-with-lease est le force push sécurisé, uniquement après un amend/rebase sur une branche personnelle
  • Jamais --force sur une branche partagée
  • pull.rebase = true évite d'avoir à taper --rebase à chaque fois
  • Diagnostiquez avec git log --left-right HEAD...origin/main avant d'agir dans le doute

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn