Comment puis-je pousser la validation modifiée vers le référentiel Git distant?

663

Quand j'ai un peu travaillé avec mon code source, j'ai fait ma validation habituelle, puis j'ai poussé vers un référentiel distant. Mais j'ai remarqué que j'avais oublié d'organiser mes importations dans le code source. Je fais donc la commande amend pour remplacer le commit précédent:

> git commit --amend

Malheureusement, la validation ne peut pas être repoussée dans le référentiel. Il est rejeté comme ceci:

> git push origin
To //my.remote.repo.com/stuff.git/
 ! [rejected]        master -> master (non-fast forward)
error: failed to push some refs to '//my.remote.repo.com/stuff.git/'

Que devrais-je faire? (Je peux accéder au référentiel distant.)

Spoike
la source
Et si mon --amend ne devait changer que le message de commit? Est-il possible de modifier seul le dernier message de validation, s'il a déjà été poussé vers la télécommande? J'ai fait ça sur Github et j'ai reçu le même message à propos de l'avance rapide. Ensuite, j'ai appliqué une solution ci-dessous, mais la fusion a simplement ajouté plus de messages de validation en haut.
7
@faB: Je pense que c'est une FAQ. Un message de validation est haché avec la validation, donc sa modification modifie le revid (hachage). Si ce n'est pas clair: non, vous ne pouvez pas. L'IIRC peut stocker des informations hors bande dans des notes (vous pouvez donc annoter les validations existantes sans les modifier). Pour étiqueter commits spécifique, les étiquettes d'utilisation
sehe
1
Vous pourrez bientôt (git1.8.5, Q4 2013) pouvoir faire git push -forceplus attentivement .
VonC
3
Voici le style cowboy. N'apprenez pas plus ou ne cherchez pas des moyens d'annuler la modification précédente. Ajoutez simplement du code d'espace réservé, je veux dire, ajoutez un commentaire, nettoyez un peu de code ou ajoutez simplement quelques tirets tirets ... Maintenant, faites un vrai commit et poussez-le à distance. Terminé !
nehem
@ user58777 Si votre -amend consistait uniquement à modifier le message de validation et que vous n'avez effectué aucune validation locale supplémentaire depuis, vous pouvez réinitialiser votre branche locale sur la validation distante que vous avez poussée avant de modifier le message de validation.
Scott Ahten du

Réponses:

504

En fait, j'ai poussé une fois avec --forceet .gitréférentiel et ai été grondé par Linus BIG TIME . En général, cela créera beaucoup de problèmes pour d'autres personnes. Une réponse simple est "Ne le faites pas".

Je vois que d'autres ont donné la recette pour le faire de toute façon, donc je ne les répéterai pas ici. Mais voici une astuce pour récupérer de la situation après avoir poussé le commit modifié avec --force (ou + master).

  1. Utilisez git reflogpour trouver l'ancien commit que vous avez modifié (appelez-le old, et nous appellerons le nouveau commit que vous avez créé en modifiant new).
  2. Créez une fusion entre oldet new, en enregistrant l'arbre de new, comme git checkout new && git merge -s ours old.
  3. Fusionnez cela avec votre maître avec git merge master
  4. Mettez à jour votre maître avec le résultat avec git push . HEAD:master
  5. Poussez le résultat.

Ensuite , les gens qui étaient assez malheureux pour avoir fondé leur travail sur la commettras vous oblitérée en modifiant et en forçant une poussée verra la fusion résultante verra que vous favorisez newplus old. Leurs fusions ultérieures ne verront pas les conflits entre oldet newqui ont résulté de votre modification, donc ils n'ont pas à souffrir.

João Pimentel Ferreira
la source
17
Je sais très bien ce qui se passe lorsque vous forcez à pousser un commit modifié (en détruisant l'historique). Heureusement, j'étais le seul développeur du projet avec le dépôt à distance sur un lecteur réseau, donc ce n'était pas si grave. Je n'ai jamais pensé à fusionner un commit de modification, je vais donc voter contre.
Spoike
62
Dans notre entreprise, nous poussons assez régulièrement ... sur des branches de fonctionnalités développées par des particuliers.
Ondra Žižka
2
La réprimande de Linus était parce que vous avez effacé l'histoire avec l'option de force, pas parce que vous ne devriez pas le faire. La solution de GabrielleV fonctionne bien, car elle ne change pas l'histoire.
user411279
2
S'il vous plaît, puisque l'auteur (gitster) de cette réponse semble ne plus exister, quelqu'un pourrait-il aider à clarifier l'élément numéro 1: retrouver l'ancien commit. Si vous n'avez pas de sauvegarde, où la trouverez-vous? Modifier et pousser de force ne l'aurait-il pas détruit? Peut-être qu'il fait référence à l'obtenir d'un ami / collaborateur qui l'a toujours dans l'arbre?
Dr Beco
2
Dr Breco, vous pouvez l'utiliser git reflogpour le trouver
Simon Zyx
269

Vous voyez une fonctionnalité de sécurité Git. Git refuse de mettre à jour la branche distante avec votre branche, car le commit de tête de votre branche n'est pas un descendant direct du commit de tête actuel de la branche vers laquelle vous poussez.

Si ce n'était pas le cas, alors deux personnes poussant vers le même référentiel à peu près en même temps ne sauraient pas qu'un nouveau commit arrivait en même temps et celui qui poussait le dernier perdrait le travail du pousseur précédent sans aucun des deux les réalisant cela.

Si vous savez que vous êtes la seule personne à pousser et que vous souhaitez pousser un commit modifié ou pousser un commit qui remonte la branche, vous pouvez 'forcer' Git à mettre à jour la branche distante à l'aide du -fcommutateur.

git push -f origin master

Même cela peut ne pas fonctionner car Git permet aux référentiels distants de refuser les poussées non rapides à l'extrémité distante en utilisant la variable de configuration receive.denynonfastforwards. Si tel est le cas, la raison du rejet ressemblera à ceci (notez la partie «rejetée à distance»):

 ! [remote rejected] master -> master (non-fast forward)

Pour contourner ce problème, vous devez soit modifier la configuration du référentiel distant, soit en tant que hack sale, vous pouvez supprimer et recréer la branche ainsi:

git push origin :master
git push origin master

En général, le dernier paramètre à git pushutiliser le format <local_ref>:<remote_ref>, où local_refest le nom de la branche sur le référentiel local et remote_refle nom de la branche sur le référentiel distant. Cette paire de commandes utilise deux raccourcis. :mastera un local_ref nul qui signifie pousser une branche nulle vers le côté distant master, c'est-à-dire supprimer la branche distante. Un nom de branche sans aucun :moyen pousse la branche locale portant le nom donné vers la branche distante du même nom. masterdans cette situation est court pour master:master.

CB Bailey
la source
2
cela ne fonctionnait pas avec github, cela m'a donné le message suivant: [rejeté à distance] master (suppression de la branche actuelle interdite)
vedang
Je ne voulais pas forcer la poussée (ce qui, je le savais, résoudrait le problème), mais maintenant je suppose que je n'ai pas le choix.
vedang
1
C'est la seule solution qui a fonctionné pour mon dépôt hébergé avec assembla.
Justin
1
la suppression de la branche maître distante libérera de l'espace dans le référentiel distant?
Mr_and_Mrs_D
1
@Mr_and_Mrs_D: Pas immédiatement, mais après une git gcfois que les reflogs ont expiré, les anciens objets seront élagués. Personne qui clone le référentiel n'aura d'objet qui ne sera plus accessible dès que la branche aura été mise à jour.
CB Bailey
211

Déclamation rapide: Le fait que personne n'ait publié la réponse simple ici démontre l'hostilité désespérée des utilisateurs manifestée par la CLI Git.

Quoi qu'il en soit, la façon "évidente" de faire cela, en supposant que vous n'avez pas essayé de forcer la poussée, est de tirer en premier. Cela tire le changement que vous avez modifié (et donc plus) pour que vous l'ayez à nouveau.

Une fois que vous avez résolu tous les conflits, vous pouvez recommencer.

Donc:

git pull

Si vous obtenez des erreurs lors de l'extraction, peut-être que quelque chose ne va pas dans la configuration de votre référentiel local (j'avais une mauvaise référence dans la section de la branche .git / config).

Et après

git push

Peut-être obtiendrez-vous un commit supplémentaire avec le sujet racontant une "fusion triviale".

GabrieleV
la source
2
Oui, j'ai écrit à ce sujet, voir stackoverflow.com/questions/253055/… ;)
Spoike
10
Cela ne fonctionne pas vraiment comme je m'y attendais. Il crée deux nouveaux commits. Celui qui est une réplique de l'ancien, mais avec les modifications modifiées. Et un merge commit avec un diff vide. Laissant toujours l'ancien commit inchangé, révélant des données potentiellement sensibles que j'essayais de modifier. Je crois git push -fou git resetest le seul moyen d'aller ici.
Thee
40
Tout en répondant techniquement au problème, il ne résout pas vraiment le problème. Comme vous l'avez dit, cela générera un commit supplémentaire, mais la principale raison pour laquelle les gens modifient un commit est d'éviter d'en créer un nouveau. Donc, si l'affiche devait suivre vos instructions, il n'obtiendrait pas le résultat souhaité. Il serait tout aussi logique de ne pas modifier l'engagement en premier lieu.
Dan Jones
102

Réponse courte: ne pas pousser les commits modifiés vers un dépôt public.

Réponse longue: quelques commandes Git, comme git commit --amendetgit rebase , réécrivent en fait le graphique d'historique. C'est bien tant que vous n'avez pas publié vos modifications, mais une fois que vous l'avez fait, vous ne devriez vraiment pas fouiner avec l'historique, car si quelqu'un a déjà obtenu vos modifications, alors quand il essaie de tirer à nouveau, il peut échouer . Au lieu de modifier une validation, vous devez simplement effectuer une nouvelle validation avec les modifications.

Cependant, si vous voulez vraiment, vraiment pousser un commit modifié, vous pouvez le faire comme ceci:

$ git push origin +master:master

Le +signe de tête forcera la poussée à se produire, même si elle n'entraîne pas de validation "d'avance rapide". (Une validation rapide se produit lorsque les modifications que vous appuyez sont un descendant direct des modifications déjà présentes dans le référentiel public.)

mipadi
la source
5
En quoi est-ce différent (meilleur ou pire) que git push -f? Merci!
bentford
11
@bentford: C'est essentiellement la même chose que git push -f.
mipadi
54

Voici un moyen très simple et propre de pousser vos modifications après avoir déjà effectué un commit --amend:

git reset --soft HEAD^
git stash
git push -f origin master
git stash pop
git commit -a
git push origin master

Ce qui fait ce qui suit:

  • Réinitialisez la tête de branche sur la validation parent.
  • Cachez ce dernier commit.
  • Forcer la poussée vers la télécommande. La télécommande n'a plus le dernier commit.
  • Pop votre cachette.
  • Engagez-vous proprement.
  • Poussez vers la télécommande.

N'oubliez pas de changer «origine» et «maître» si vous appliquez cela à une autre succursale ou à une autre télécommande.

Faiza
la source
3
2 remarques: - assurez-vous de changer le nom de la branche si vous travaillez sur une autre - je devais utiliser git addavant mon commit pour inclure les changements.
SylvainB
1
Dans Windows CMD, la première commande doit être échappé: git reset --soft "HEAD^". Le reste fonctionne bien.
MrMister
2
"une manière très simple et propre .." cit. Cette procédure comprend une poussée forcée. À la lumière de toutes les critiques dans les réponses ci-dessus, je ne sais pas si cette procédure est en fait propre.
Na13-c
24

Je l'ai résolu en supprimant mon commit modifié local et en ajoutant les nouvelles modifications en haut:

# Rewind to commit before conflicting
git reset --soft HEAD~1

# Pull the remote version
git pull

# Add the new commit on top
git add ...
git commit
git push
bara
la source
2
Ceci est la version la plus simple!
mknaf
Il est préférable d'ajouter un autre commit de «changement» que de jouer avec l'historique de réécriture. Je suis d'accord avec @mknaf
sdkks
8

J'ai eu le même problème.

  • Modification accidentelle du dernier commit déjà poussé
  • Fait beaucoup de changements localement, commis environ cinq fois
  • J'ai essayé de pousser, j'ai eu une erreur, j'ai paniqué, j'ai fusionné la télécommande, j'ai reçu beaucoup de fichiers non-enregistrés, poussé, échoué, etc.

En tant que débutant Git, je pensais que c'était FUBAR complet .

Solution: un peu comme @bara a suggéré + créé une branche de sauvegarde locale

# Rewind to commit just before the pushed-and-amended one.
# Replace <hash> with the needed hash.
# --soft means: leave all the changes there, so nothing is lost.
git reset --soft <hash>

# Create new branch, just for a backup, still having all changes in it.
# The branch was feature/1234, new one - feature/1234-gone-bad
git checkout -b feature/1234-gone-bad

# Commit all the changes (all the mess) not to lose it & not to carry around
git commit -a -m "feature/1234 backup"

# Switch back to the original branch
git checkout feature/1234

# Pull the from remote (named 'origin'), thus 'repairing' our main problem
git pull origin/feature/1234

# Now you have a clean-and-non-diverged branch and a backup of the local changes.
# Check the needed files from the backup branch
git checkout feature/1234-gone-bad -- the/path/to/file.php

Ce n'est peut-être pas une solution rapide et propre, et j'ai perdu mon historique (1 commit au lieu de 5), mais cela a sauvé une journée de travail.

davisca
la source
6

Si vous n'avez pas poussé le code vers votre branche distante (GitHub / Bitbucket), vous pouvez modifier le message de validation sur la ligne de commande comme ci-dessous.

 git commit --amend -m "Your new message"

Si vous travaillez sur une branche spécifique, procédez comme suit:

git commit --amend -m "BRANCH-NAME: new message"

Si vous avez déjà poussé le code avec un mauvais message, vous devez être prudent lorsque vous modifiez le message. Par exemple, après avoir modifié le message de validation et essayé de le pousser à nouveau, vous vous retrouvez avec des problèmes. Pour le rendre lisse, suivez les étapes suivantes.

Veuillez lire l'intégralité de la réponse avant de le faire

git commit --amend -m "BRANCH-NAME : your new message"

git push -f origin BRANCH-NAME                # Not a best practice. Read below why?

Remarque importante: lorsque vous utilisez directement la poussée forcée, vous pouvez vous retrouver avec des problèmes de code que d'autres développeurs travaillent sur la même branche. Donc, pour éviter ces conflits, vous devez extraire le code de votre branche avant d'effectuer une poussée forcée :

 git commit --amend -m "BRANCH-NAME : your new message"
 git pull origin BRANCH-NAME
 git push -f origin BRANCH-NAME

Il s'agit de la meilleure pratique lors de la modification du message de validation, s'il a déjà été poussé.

Prabhakar Undurthi
la source
1
Si vous avez réussi à retirer la validation dans votre dernier exemple, pourquoi devez-vous forcer la poussée? Une poussée standard ne suffirait-elle pas? Merci
Thomas
La question posée par Thomas est en fait très valable. Moi-même, je n'avais pas besoin de forcer la poussée après la traction.
Na13-c
S'il vous plaît ne l'appelez pas "la meilleure pratique" car il existe un moyen de contourner --forcele problème, voir la réponse acceptée
Farid
5

Si vous savez que personne n'a retiré votre commit non modifié, utilisez l' --force-with-leaseoption de git push.

Dans TortoiseGit, vous pouvez faire la même chose sous "Push ..." options "Force: May discard" et vérification des "changements connus".

Force (peut ignorer les modifications connues) permet au référentiel distant d'accepter une poussée non rapide plus sûre. Cela peut entraîner la perte de validations du référentiel distant; utilisez-le avec précaution. Cela peut éviter de perdre des modifications inconnues d'autres personnes sur la télécommande. Il vérifie si la branche du serveur pointe vers le même commit que la branche de suivi à distance (changements connus). Si oui, une poussée forcée sera effectuée. Sinon, il sera rejeté. Étant donné que git n'a pas de balises de suivi à distance, les balises ne peuvent pas être remplacées à l'aide de cette option.

ShawnFeatherly
la source
4

Vous obtenez cette erreur car la télécommande Git a déjà ces fichiers de validation. Vous devez forcer la poussée de la branche pour que cela fonctionne:

git push -f origin branch_name

Assurez-vous également que vous extrayez le code à distance, car quelqu'un d'autre de votre équipe aurait pu pousser vers la même branche.

git pull origin branch_name

C'est l'un des cas où nous devons forcer le push sur commit à distance.

Praveen Dhawan
la source
Pourquoi cette réponse ne tient-elle pas compte des principaux commentaires soulevés dans les réponses précédentes?
Na13-c
2

Voici un moyen très simple et propre de pousser vos modifications après avoir déjà fait un git add "your files"et git commit --amend:

git push origin master -f

ou:

git push origin master --force
fou
la source
J'entends que c'est mauvais, et je suis sûr que ça l'est. Il y a une (bonne) raison pour que git échoue par défaut (et nécessite --force), j'en suis sûr.
Rolf
1

J'ai dû résoudre ce problème en tirant du référentiel distant et gérer les conflits de fusion qui se sont produits, commettre puis pousser. Mais j'ai l'impression qu'il y a une meilleure façon.

Spoike
la source
Pas vraiment. Le problème peut être que vous n'avez pas mis à jour votre copie locale à partir du référentiel distant. Git ne le poussera pas car vous devrez peut-être gérer les fusions manuellement. Dans mon autre réponse, j'ai une commande (et une explication) qui forcera une poussée - mais attention, cela peut supprimer les modifications de la télécommande.
mipadi
1

J'ai continué à faire ce que Git m'a dit de faire. Donc:

  • Impossible de pousser en raison d'un commit modifié.
  • Je fais un pull comme suggéré.
  • La fusion échoue. donc je le répare manuellement.
  • Créez un nouveau commit (étiqueté "merge") et poussez-le.
  • Cela semble fonctionner!

Remarque: le commit modifié était le dernier.

Rolf
la source
1
Je dévaloriserais, si j'avais plus de points de réputation, donc je demanderais ici poliment, lequel était-il parmi les malades? Celui qui a modifié? Celui qui a tiré et travaillé sur une branche avec un engagement modifié? Avant ou après l'amendement? Je viens d'effacer chaque modification de la mienne parce que je vous ai mal compris ... Heureusement, il n'y avait pas grand-chose ...
Bartis Áron
1

Ce qui suit a fonctionné pour moi lors du changement d'Auteur et de Committer d'un commit.

git push -f origin master

Git était assez intelligent pour comprendre qu'il s'agissait de commits de deltas identiques qui ne différaient que dans la section méta-informations.

Les chefs locaux et distants ont indiqué les commits en question.

MadPhysicist
la source
0

Ici, comment j'ai corrigé une modification dans un commit précédent:

  1. Enregistrez votre travail jusqu'à présent.
  2. Rangez vos modifications pour l'instant si elles sont effectuées: git stashvotre copie de travail est maintenant propre à l'état de votre dernier commit.
  3. Apportez les modifications et les correctifs.
  4. Validez les modifications en mode "Modifier" :git commit --all --amend
  5. Votre éditeur vous demandera un message de journal (par défaut, l'ancien message de journal). Enregistrez et quittez l'éditeur lorsque vous en êtes satisfait.

    Les nouvelles modifications sont ajoutées à l'ancien commit. Voyez par vous-même avec git logetgit diff HEAD^

  6. Réappliquez vos modifications cachées, le cas échéant: git stash apply

Harshal Wani
la source