1880 votes

Comment puis-je correctement forcer un Git push ?

J'ai configuré un dépôt distant non-bare "main" et je l'ai cloné sur mon ordinateur. J'ai apporté des modifications locales, mis à jour mon dépôt local, et poussé les modifications vers mon dépôt distant. Jusque-là, tout allait bien.

Maintenant, j'ai dû changer quelque chose dans le dépôt distant. Ensuite, j'ai changé quelque chose dans mon dépôt local. Je me suis rendu compte que le changement dans le dépôt distant n'était pas nécessaire. J'ai donc essayé de faire un git push de mon dépôt local vers mon dépôt distant, mais j'ai obtenu une erreur comme :

Pour éviter que vous ne perdiez de l'historique, les mises à jour non "fast-forward" ont été rejetées. Fusionnez les changements distants avant de repousser. Consultez la section 'Note à propos des fast-forwards' de git push --help pour plus de détails.

J'ai pensé qu'en utilisant peut-être un

git push --force

cela forcerait ma copie locale à pousser les modifications vers celle du dépôt distant et à les rendre identiques. Cela force la mise à jour, mais lorsque je retourne au dépôt distant et que je fais un commit, je remarque que les fichiers contiennent des changements obsolètes (ceux que le dépôt principal distant avait précédemment).

Comme je l'ai mentionné dans les commentaires sur l'une des réponses:

[J'ai] essayé de forcer, mais en revenant au serveur principal pour sauvegarder les changements, j'obtiens une mise en scène obsolète. Ainsi, lorsque je commite, les dépôts ne sont pas les mêmes. Et lorsque j'essaie d'utiliser git push à nouveau, j'obtiens la même erreur.

Comment puis-je résoudre ce problème?

4 votes

Vous pourrez bientôt (git1.8.5, T4 2013) faire un git push -force de manière plus prudente.

14 votes

Comme je le détaille dans ma propre réponse, git push --force est en effet une autre façon valide de forcer la poussée et poussera les branches aussi bien que git push origin master --force avec les paramètres de configuration par défaut de push.default de Git, bien que les branches spécifiquement poussées diffèrent entre les versions de Git antérieures à 2.0 et celles après 2.0.

3250voto

Kayvar Points 4239

Il suffit de :

git push origin  --force

ou si vous avez un dépôt spécifique :

git push https://git.... --force

Cela supprimera votre ou vos commits précédents et poussera votre commit actuel.

Cela peut ne pas être approprié, mais si quelqu'un tombe sur cette page, pensez qu'ils pourraient vouloir une solution simple...

Drapeau court

Notez également que -f est l'abréviation de --force, donc

git push origin  -f

fonctionnera également.

78 votes

Vous pouvez utiliser git push origin +master à la place, ce qui vous permet de pousser plusieurs refspecs sans les forcer tous.

12 votes

Sachez que si vous faites accidentellement juste git push --force, vous pourriez finir par perturber votre branche principale (selon le comportement par défaut de votre push).. Ce qui pourrait être embêtant.. un peu.. :D

21 votes

@Jeewes à partir de la version Git 2.0, le comportement par défaut de git push --force est essentiellement de forcer l'envoi de la branche actuellement vérifiée vers sa contrepartie distante, donc si vous avez la branche master vérifiée, alors c'est identique à git push origin master --force. Cela sera différent si vous utilisez le réglage matching pour push.default, qui est la valeur par défaut pour les versions de Git antérieures à 2.0. matching envoie toutes les branches locales vers celles qui ont le même nom sur le distant, donc forcer l'envoi pourrait certainement ne pas être ce que vous voulez faire...

324voto

Cawas Points 3303

Et si push --force ne fonctionne pas, vous pouvez faire push --delete. Regardez la 2ème ligne sur cette instance:

git reset --hard HEAD~3  # réinitialiser la branche actuelle à 3 commits en arrière
git push origin master --delete  # faire une très très mauvaise chose
git push origin master  # push régulier

Mais attention...

Ne jamais revenir en arrière sur un historique git public !

En d'autres termes:

  • Ne jamais faire de force push sur un dépôt public.
  • Ne faites pas cela ou quoi que ce soit qui puisse casser le pull de quelqu'un.
  • Ne réinitialisez jamais ou ne réécrivez jamais l'historique dans un dépôt que quelqu'un aurait déjà pullé.

Il y a bien sûr des exceptions exceptionnellement rares même à cette règle, mais dans la plupart des cas, ce n'est pas nécessaire de le faire et cela générera des problèmes pour tout le monde.

Faites plutôt un revert.

Et soyez toujours prudent avec ce que vous poussez vers un dépôt public. Revertir :

git revert -n HEAD~3..HEAD  # préparer un nouveau commit annulant les 3 derniers commits
git commit -m "désolé - annuler les 3 derniers commits car je n'étais pas prudent "
git push origin master  # push régulier

En effet, les deux HEADs d'origine (du revert et du reset malveillant) contiendront les mêmes fichiers.


modification pour ajouter des infos mises à jour et plus d'arguments autour de push --force

Considérez pousser force avec lease au lieu de push, mais préférez toujours le revert

Un autre problème que push --force peut poser est lorsque quelqu'un pousse quelque chose avant vous, mais après que vous ayez déjà fetché. Si vous poussez de force votre version rebased maintenant, vous remplacerez le travail des autres.

git push --force-with-lease introduit dans le git 1.8.5 (merci à @VonC commentaire sur la question) essaye de résoudre ce problème spécifique. Fondamentalement, il apportera une erreur et ne poussera pas si le remote a été modifié depuis votre dernier fetch.

C'est bien si vous êtes vraiment sûr qu'un push --force est nécessaire, mais que vous voulez toujours éviter plus de problèmes. J'irais jusqu'à dire que ce devrait être le comportement par défaut de push --force. Mais ce n'est toujours pas une excuse pour forcer un push. Les personnes qui ont fetché avant votre rebase auront toujours beaucoup de problèmes, qui pourraient être facilement évités si vous aviez reverti à la place.

Et puisque nous parlons d'instances git --push...

Pourquoi quelqu'un voudrait-il forcer un push ?

@linquize a apporté un bon exemple de force push dans les commentaires : données sensibles. Vous avez accidentellement divulgué des données qui ne devraient pas être poussées. Si vous êtes assez rapide, vous pouvez "corriger"* cela en forçant un push par-dessus.

* Les données seront toujours sur le serveur distant à moins que vous ne fassiez également un garbage collect, ou effectuez un nettoyage d'une manière ou d'une autre. Il y a aussi le potentiel évident pour qu'elles soient diffusées par d'autres qui les ont fetchées déjà, mais vous avez compris l'idée.

0 votes

On dirait qu'il est possible de faire un pull après cela, mais difficile stackoverflow.com/questions/9813816/…

1 votes

Le problème, @rogerdpack, n'est pas si c'est faisable. C'est le cas. Mais cela peut se résumer à un gros désastre. Plus quelqu'un le fait (poussée forcée) et moins souvent vous mettez à jour (tirez) du dépôt public, plus le désastre est grand. Cela peut démanteler le monde tel que vous le connaissez !!!111 Au moins le monde comprenant ce dépôt particulier.

6 votes

Si vous avez des données sensibles, poussez-les de force.

22voto

Cupcake Points 22154

Pour en savoir plus sur le force pushing, consultez ma autre réponse.

Cette réponse explique que le problème fondamental du demandeur original n'est pas la manière dont il effectue le force pushing, mais plutôt qu'il pousse vers un dépôt non-bare (au lieu d'un dépôt bare).

Comme indiqué dans d'autres réponses1, il n'est généralement pas recommandé de pousser vers des dépôts non-bare, car bien que vous puissiez mettre à jour une référence de branche distante en poussant, les poussées Git n'actualiseront pas réellement la copie de travail ni l'index/zone de staging du dépôt non-bare distant.

Cela signifie que la copie de travail et l'index/zone de staging du dépôt distant seront toujours définis sur les versions des fichiers qui existaient avant que vous ayez poussé, tandis que la référence symbolique HEAD sera définie sur la nouvelle pointe de la branche poussée. Il semblera donc que les modifications du commit précédent soient toujours en attente, ce qui correspond à ce que vous avez indiqué dans votre question :

Je remarque que les fichiers contiennent des modifications obsolètes (celles qui existaient précédemment dans le dépôt distant principal).

1Voir <a href="http://stackoverflow.com/a/5509650/456814">la réponse de VonC</a> et <a href="http://stackoverflow.com/a/5509588/456814">la réponse de ubik</a>.

Comment reproduire le problème

Tout d'abord, créez un nouveau dépôt Git non-bare, et remplissez-le avec quelques fichiers :

mkdir main
cd main
git init
echo hello > hello.txt
git add .
git commit -m "Hello"

echo bye > bye.txt
git add .
git commit -m "Bye"

Les versions actuelles de Git ne vous permettent pas de pousser vers un dépôt non-bare par défaut, vous devrez donc configurer ce dépôt pour le permettre :

git config receive.denyCurrentBranch warn

Ensuite, faites un autre clone, et effectuez un hard reset d'un commit en arrière, puis poussez :

$ cd ..
$ git clone main second
$ git reset --hard head^
$ git push --force
Total 0 (delta 0), reused 0 (delta 0)
remote: avertissement : mise à jour de la branche actuelle
To main
 + 3c3ee2b...2f34b62 master -> master (mise à jour forcée)

Retournez maintenant au dépôt principal, et vérifiez à la fois les journaux et l'état des fichiers. Vous verrez que même si la branche master est revenu au commit original, la zone de staging contient toujours les modifications du deuxième commit qui ont été annulées dans la branche par le force push :

$ cd ../main
$ git log --oneline --decorate
2f34b62 (HEAD, master) Ajout de hello.txt

$ git status
On branch master
Changes to be committed:
  (utilisez "git reset HEAD ..." pour désindexer)

        nouveau fichier:   bye.txt

Solution

Si vous souhaitez que la copie de travail et l'index/zone de staging dans le dépôt distant non-bare correspondent au commit actuel de la branche actuelle (représenté par la référence symbolique HEAD), il vous suffit de faire un hard reset à HEAD :

git reset --hard HEAD

Enfin, il est recommandé de pousser uniquement vers des dépôts bare au lieu de dépôts non-bare, en raison de ce problème de désynchronisation de la copie de travail et de l'index par rapport à la branche et aux références symboliques de votre dépôt.

Une manière simple de convertir un dépôt non-bare en un dépôt bare est simplement de le recréer :

git clone --bare originalRepo bareRepo
cd bareRepo
git remote rm origin

Documentation

D'après la page manuelle git-config(1) (mes emphases) :

receive.denyCurrentBranch

Si défini sur true ou "refuse", git-receive-pack refusera une mise à jour de référence vers la branche actuellement vérifiée d'un dépôt non-bare. Un tel push est potentiellement dangereux car il désynchronise HEAD avec l'index et l'arborescence de travail. S'il est défini sur "warn", imprime un avertissement de ce type de poussée sur stderr, mais permet à la poussée de continuer. S'il est défini sur false ou "ignore", autorise de tels push sans message. Par défaut, il est défini sur "refuse".

Ce paramètre est disponible dans Git depuis la version 1.6.2, qui a été publiée le 4 mars 2009. Comme l'indique la documentation ci-dessus, le paramètre se configure par défaut pour refuser les poussées, même les force-pushes.

Démonstration que le force pushing n'est pas une solution au problème réel du demandeur original

Parce qu'il semble y avoir beaucoup de confusion à ce sujet, je vais démontrer que le force-pushing n'est pas la bonne solution pour résoudre votre problème.

Tout d'abord, créez un dépôt principal exactement comme dans les étapes de reproduction précédentes ci-dessus, ainsi qu'un deuxième clone de ce dépôt principal, mais cette fois, ne définissez pas receive.denyCurrentBranch. Ce paramètre refusera les poussées, même les force-pushes, vers les dépôts distants non-bare, et a été le paramètre par défaut depuis Git version 1.6.2.

Ensuite, depuis le deuxième clone, essayez de forcer la poussée vers le dépôt principal :

$ git push --force
Total 0 (delta 0), reused 0 (delta 0)
error: refus de mettre à jour la branche actuellement vérifiée : refs/heads/master
error: Par défaut, la mise à jour de la branche actuelle dans un dépôt non-bare
error: est refusée, car elle rendra l'index et l'arborescence de travail inconsistants
error: avec ce que vous avez poussé, et nécessitera 'git reset --hard' pour correspondre
error: l'arborescence de travail à HEAD.
error:
error: Vous pouvez définir la variable de configuration 'receive.denyCurrentBranch' à
error: 'ignore' ou 'warn' dans le dépôt distant pour autoriser une poussée dans
error: sa branche actuelle ; cependant, ce n'est pas recommandé à moins que vous
error: ne puissiez mettre à jour son arborescence de travail pour correspondre à ce que vous avez poussé d'une autre manière.
error:
error: Pour masquer ce message tout en conservant le comportement par défaut, définissez
error: la variable de configuration 'receive.denyCurrentBranch' sur 'refuse'.
To main
 ! [rejected] master -> master (la branche est actuellement vérifiée)
error: échec de la transmission de certaines références vers 'main'

Ou avec git push origin master --force :

$ git push origin master --force
Total 0 (delta 0), reused 0 (delta 0)
error: refus de mettre à jour la branche actuellement vérifiée : refs/heads/master
error: Par défaut, la mise à jour de la branche actuelle dans un dépôt non-bare
error: est refusée, car elle rendra l'index et l'arborescence de travail inconsistants
error: avec ce que vous avez poussé, et nécessitera 'git reset --hard' pour correspondre
error: l'arborescence de travail à HEAD.
error:
error: Vous pouvez définir la variable de configuration 'receive.denyCurrentBranch' à
error: 'ignore' ou 'warn' dans le dépôt distant pour autoriser une poussée dans
error: sa branche actuelle ; cependant, ce n'est pas recommandé à moins que vous
error: ne puissiez mettre à jour son arborescence de travail pour correspondre à ce que vous avez poussé d'une autre manière.
error:
error: Pour masquer ce message tout en conservant le comportement par défaut, définissez
error: la variable de configuration 'receive.denyCurrentBranch' sur 'refuse'.
To main
 ! [rejected] master -> master (la branche est actuellement vérifiée)
error: échec de la transmission de certaines références vers 'main'

Comme vous pouvez le constater ci-dessus, peu importe la manière dont vous essayez de forcer la poussée vers le dépôt distant non-bare, Git ne l'autorisera pas par défaut. Même lorsque vous définissez receive.denyCurrentBranch pour autoriser les force-pushes, cela ne touchera toujours pas à l'index et l'arborescence de travail du dépôt non-bare distant, ce qui rend

git reset --hard HEAD

la solution correcte lorsque la copie de travail et l'index/zone de staging d'un dépôt distant non-bare deviennent désynchronisés avec la référence symbolique HEAD.

20voto

ubik Points 2361

Tout d'abord, je ne ferais aucun changement directement dans le dépôt "main". Si vous voulez vraiment avoir un dépôt "main", alors vous ne devriez que pousser dedans, ne jamais le modifier directement.

En ce qui concerne l'erreur que vous obtenez, avez-vous essayé git pull depuis votre dépôt local, puis git push vers le dépôt principal? Ce que vous faites actuellement (si j'ai bien compris) est de forcer la poussée et de perdre ensuite vos changements dans le dépôt "main". Vous devriez d'abord fusionner les changements localement.

1 votes

Oui j'ai essayé un pull mais je perds des données à cause de ce pull. Je veux que mes dépôts principaux soient comme mes locaux, sans d'abord mettre à jour depuis le principal.

3 votes

Dans ce cas, utilisez git push -f, mais ensuite si vous changez à nouveau votre dépôt principal, vous devez retourner à votre dépôt local et faire git pull, afin qu'il soit synchronisé avec les dernières modifications. Ensuite, vous pouvez faire votre travail et pousser à nouveau. Si vous suivez ce flux de travail "push-pull", vous ne rencontrerez pas le genre d'erreur dont vous vous plaigniez.

0 votes

Oui, je comprends que c'était ma faute :/ Je vais essayer cela et je reviendrai dans un petit moment, merci

12voto

Cupcake Points 22154

Cette réponse explique le force pushing. Le problème réel de l'auteur original n'était pas le force pushing lui-même, mais pousser vers un dépôt non nu (au lieu d'un dépôt nu), comme expliqué dans ma autre réponse.

Vous avez 4 façons de forcer le push avec Git

git push   -f
git push origin master -f # Example

git push  -f
git push origin -f # Example

git push -f

git push   --force-with-lease

Si vous voulez une explication plus détaillée de chaque commande, consultez ma section de longues réponses ci-dessous.

Attention: le force pushing écrasera la branche distante avec l'état de la branche que vous poussez. Assurez-vous que c'est vraiment ce que vous voulez faire avant de l'utiliser, sinon vous risquez d'écraser des commits que vous voulez en réalité conserver.

Détails du force pushing

Spécification du remote et de la branche

Vous pouvez spécifier complètement des branches spécifiques et un remote. Le drapeau -f est la version courte de --force

git push   --force
git push   -f

Omission de la branche

Lorsque la branche à pousser est omise, Git la déterminera en fonction de vos paramètres de configuration. Dans les versions de Git après 2.0, un nouveau dépôt aura des paramètres par défaut pour pousser la branche actuellement cochée :

git push  --force

tandis que avant 2.0, les nouveaux dépôts auront des réglages par défaut pour pousser plusieurs branches locales. Les réglages en question sont les paramètres remote..push et push.default (voir ci-dessous).

Omission du remote et de la branche

Lorsque à la fois le remote et la branche sont omis, le comportement de simplement git push --force est déterminé par vos paramètres de configuration Git push.default :

git push --force
  • A partir de Git 2.0, le réglage par défaut, simple, poussera essentiellement votre branche actuelle vers sa contrepartie distante en amont. Le remote est déterminé par le paramètre branch..remote de la branche, et par défaut le repo origin sinon.

  • Avant la version 2.0 de Git, le réglage par défaut, matching, poussera essentiellement toutes vos branches locales vers des branches portant le même nom sur le remote (qui est par défaut origin).

Vous pouvez en apprendre davantage sur les réglages push.default en lisant git help config ou une version en ligne de la page de manuel git-config(1).

Forcer le push de manière plus sûre avec --force-with-lease

Forcer le push avec un "leasing" permet au force push de d'échouer s'il y a de nouveaux commits sur le remote que vous n'attendiez pas (techniquement, si vous ne les avez pas encore fetch dans votre branche de suivi distante), ce qui est utile si vous ne voulez pas accidentellement écraser les commits de quelqu'un d'autre que vous ne connaissiez même pas encore, et que vous voulez simplement remplacer les vôtres :

git push   --force-with-lease

Note, cependant, que comme il s'agit toujours d'un force push, cela peut poser un problème pour vos collaborateurs s'ils sont incapables ou non disposés à resynchroniser leurs commits existants avec vos commits force-pushed.

Vous pouvez en savoir plus sur comment utiliser --force-with-lease en lisant un de ces éléments :

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X