443 votes

Supprimer un commit spécifique

Je travaillais avec un ami sur un projet, et il a édité un tas de fichiers qui n'auraient pas dû être édités. D'une manière ou d'une autre, j'ai fusionné son travail avec le mien, soit quand je l'ai retiré, soit quand j'ai essayé de choisir les fichiers spécifiques que je voulais. J'ai cherché et joué pendant longtemps, en essayant de comprendre comment supprimer les commits qui contiennent les modifications de ces fichiers, il semble que ce soit un mélange entre revert et rebase, et il n'y a pas d'exemples simples, et les docs supposent que je sais plus que je ne sais.

Voici donc une version simplifiée de la question :

Compte tenu du scénario suivant, comment puis-je supprimer le commit 2 ?

$ mkdir git_revert_test && cd git_revert_test

$ git init
Initialized empty Git repository in /Users/josh/deleteme/git_revert_test/.git/

$ echo "line 1" > myfile

$ git add -A

$ git commit -m "commit 1"
[master (root-commit) 8230fa3] commit 1
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 myfile

$ echo "line 2" >> myfile

$ git commit -am "commit 2"
[master 342f9bb] commit 2
 1 files changed, 1 insertions(+), 0 deletions(-)

$ echo "line 3" >> myfile

$ git commit -am "commit 3"
[master 1bcb872] commit 3
 1 files changed, 1 insertions(+), 0 deletions(-)

Le résultat attendu est

$ cat myfile
line 1
line 3

Voici un exemple de la façon dont j'ai essayé de revenir en arrière.

$ git revert 342f9bb
Automatic revert failed.  After resolving the conflicts,
mark the corrected paths with 'git add <paths>' or 'git rm <paths>'
and commit the result.

15 votes

Si quelqu'un trouve ceci en cherchant le même problème, voici ce que j'ai fini par faire : Copier et coller. Sérieusement. J'ai passé plus de 6 heures à essayer de faire fonctionner les solutions proposées, en vain. A la fin, je n'avais plus le temps, j'ai sorti l'original et j'ai juste copié/collé une vingtaine de fichiers. Cela m'a pris moins de 5 minutes, et tout s'est bien passé depuis (même lorsque ces fichiers sont fusionnés avec des changements dans d'autres branches qui ont eu lieu avant ce fiasco). Je vous suggère d'adopter cette approche également. Non seulement c'est la plus simple, mais je pense aussi que c'est la seule chose qui fonctionne.

0 votes

J'ai été confronté à un problème similaire, mais peut-être plus complexe : j'avais une branche avec des centaines de commits que je voulais écraser. Malheureusement, les commits d'une autre branche ont été fusionnés dans la branche à un point intermédiaire, donc un "unmerge" était nécessaire avant que je puisse écraser. J'ai suivi un chemin similaire à celui suggéré par tk ci-dessous (cherry picking + utilisation de la notation de l'intervalle), mais cela a produit des conflits dans certains autres fichiers. En fin de compte, le copier-coller et quelques modifications manuelles ont été le moyen le plus facile et le plus prévisible de progresser. Cela vaut vraiment la peine d'être considéré si vous passez trop de temps sur ce sujet.

1 votes

Un problème que j'ai toujours, c'est que je ne suis jamais la personne qui commet le problème. Je suis le Release Manager et nos développeurs viennent me voir pour corriger les choses. Je n'ai jamais les commits à corriger dans mon local, donc beaucoup d'hypothèses "ex : si VOUS avez inclus le mauvais commit" ne s'appliquent pas vraiment. Mon clone local n'a jamais l'historique avec lequel travailler.

423voto

user1121352 Points 1066

Il existe quatre façons de procéder :

  • Une manière propre, un retour en arrière mais garder dans le journal le retour en arrière :

    git revert --strategy resolve <commit>
  • La méthode la plus dure consiste à ne supprimer que le dernier commit :

    git reset --soft "HEAD^"

Note : Évitez git reset --hard car cela supprimera également toutes les modifications apportées aux fichiers depuis la dernière livraison. Si --soft ne fonctionne pas, essayez plutôt --mixed o --keep .

  • Rebase (affichez le journal des 5 derniers commits et supprimez les lignes que vous ne voulez pas, ou réordonnez, ou écrasez plusieurs commits en un seul, ou faites tout ce que vous voulez, c'est un outil très polyvalent) :

    git rebase -i HEAD~5

Et si une erreur est commise :

git rebase --abort
  • Quick rebase : supprime seulement un commit spécifique en utilisant son id :

    git rebase --onto commit-id^ commit-id
  • Alternatives : vous pouvez également essayer :

    git cherry-pick commit-id
  • Une autre alternative encore :

    git revert --no-commit
  • En dernier recours, si vous avez besoin d'une liberté totale d'édition de l'historique (par exemple, parce que git ne vous permet pas d'éditer ce que vous voulez), vous pouvez utiliser ceci très rapide application open source : reposurgeon .

Note : bien sûr, toutes ces modifications sont effectuées localement, vous devez git push après pour appliquer les changements à la télécommande. Et dans le cas où votre repo ne veut pas supprimer le commit ("no fast-forward allowed", ce qui arrive quand vous voulez supprimer un commit que vous avez déjà poussé), vous pouvez utiliser git push -f pour forcer les changements.

Note2 : si vous travaillez sur une branche et que vous avez besoin de forcer le push, vous devez absolument éviter git push --force car cela peut écraser d'autres branches (si vous y avez apporté des modifications, même si votre checkout actuel est sur une autre branche). Préférez toujours spécifier la branche distante lorsque vous forcez le push : git push --force origin your_branch .

1 votes

Basé sur les suggestions de @gaborous : faites un "git rebase -i HEAD~2". Maintenant vous avez plusieurs options. Dans vim, vous pouvez voir quelques lignes commentées : l'une d'elles vous indique que vous pouvez simplement supprimer une ligne (qui devrait être le commit dont vous voulez vous débarrasser) et ce commit sera supprimé avec son log dans votre historique.

2 votes

Git revert --strategy resolve <commit> .Cette commande a fonctionné pour moi. merci :)

7 votes

git rebase -i HEAD~5 a fonctionné pour moi. Ensuite, j'ai simplement supprimé le ou les commit(s) dont je n'avais pas besoin et j'ai pu résoudre le problème en moins de 30 secondes. Merci.

85voto

Hal Eisen Points 71

L'algorithme utilisé par Git pour calculer les diffs à rétablir nécessite que

  1. Les lignes qui sont inversées ne sont pas modifiées par les commits ultérieurs.
  2. Il n'y aura pas d'autres engagements "adjacents" plus tard dans l'histoire.

La définition de "adjacent" est basée sur le nombre de lignes par défaut d'un diff contextuel, qui est de 3. Donc si 'myfile' était construit comme ceci :

$ cat >myfile <<EOF
line 1
junk
junk
junk
junk
line 2
junk
junk
junk
junk
line 3
EOF
$ git add myfile
$ git commit -m "initial check-in"
 1 files changed, 11 insertions(+), 0 deletions(-)
 create mode 100644 myfile

$ perl -p -i -e 's/line 2/this is the second line/;' myfile
$ git commit -am "changed line 2 to second line"
[master d6cbb19] changed line 2
 1 files changed, 1 insertions(+), 1 deletions(-)

$ perl -p -i -e 's/line 3/this is the third line/;' myfile
$ git commit -am "changed line 3 to third line"
[master dd054fe] changed line 3
 1 files changed, 1 insertions(+), 1 deletions(-)

$ git revert d6cbb19
Finished one revert.
[master 2db5c47] Revert "changed line 2"
 1 files changed, 1 insertions(+), 1 deletions(-)

Ensuite, tout fonctionne comme prévu.

La deuxième réponse était très intéressante. Il existe une fonctionnalité qui n'a pas encore été officiellement publiée (bien qu'elle soit disponible dans Git v1.7.2-rc2) appelée Stratégie de retour en arrière. Vous pouvez invoquer git comme ceci :

git revert --stratégie résoudre <compromis>

et cela devrait permettre de mieux comprendre ce que vous vouliez dire. Je ne sais pas quelle est la liste des stratégies disponibles, ni la définition d'une quelconque stratégie.

2 votes

perl -p est utile pour écrire des programmes très courts (une seule ligne) qui bouclent et passer leur entrée jusqu'à la sortie, de façon similaire à sed. perl -i est utilisé pour l'édition de fichiers en place . perl -e est de savoir comment soumettre le code pour qu'il soit évalué .

41voto

Andrew Walker Points 1513

Vous avez le choix entre

  1. conserver l'erreur et introduire un correctif et
  2. en supprimant l'erreur et en modifiant l'historique.

Vous devriez choisir (1) si la modification erronée a été reprise par quelqu'un d'autre et (2) si l'erreur est limitée à une branche privée non poussée.

Git revert est un outil automatisé pour faire (1), il crée un nouveau commit annulant un commit précédent. Vous verrez l'erreur et la suppression dans l'historique du projet, mais les personnes qui tirent de votre dépôt ne rencontreront pas de problèmes lors de la mise à jour. Cela ne fonctionne pas de manière automatique dans votre exemple, vous devez donc éditer 'myfile' (pour supprimer la ligne 2), faire git add myfile y git commit pour gérer le conflit. Vous vous retrouverez alors avec quatre commits dans votre historique, le commit 4 annulant le commit 2.

Si personne ne se soucie que votre histoire change, vous pouvez la réécrire et supprimer le commit 2 (choix 2). La façon la plus simple de faire cela est d'utiliser git rebase -i 8230fa3 . Cela vous amènera dans un éditeur et vous pourrez choisir de ne pas inclure le commit erroné en supprimant le commit (et en gardant "pick" à côté des autres messages de commit. Lisez bien l'article les conséquences d'un tel comportement .

0 votes

Le rebasement peut être délicat, car il semble qu'il y ait eu une fusion.

3 votes

Git rebase -i 8230fa3, et supprimer la ligne du commit a bien fonctionné pour moi avec mes changements locaux seulement. Merci !

20voto

Dennis Points 5020

Vous pouvez supprimer les commits non désirés avec git rebase . Disons que vous avez inclus quelques commits de la branche topic d'un collègue dans votre branche topic, mais que vous décidez plus tard que vous ne voulez pas de ces commits.

git checkout -b tmp-branch my-topic-branch  # Use a temporary branch to be safe.
git rebase -i master  # Interactively rebase against master branch.

À ce stade, votre éditeur de texte ouvrira la vue interactive de rebasement. Par exemple

git-rebase-todo

  1. Retirez les commits que vous ne voulez pas en supprimant leurs lignes.
  2. Sauvegarder et quitter

Si le rebasement n'a pas réussi, supprimez la branche temporaire et essayez une autre stratégie. Sinon, continuez avec les instructions suivantes.

git checkout my-topic-branch
git reset --hard tmp-branch  # Overwrite your topic branch with the temp branch.
git branch -d tmp-branch  # Delete the temporary branch.

Si vous poussez votre branche topic vers une branche distante, vous pouvez avoir besoin de forcer le push puisque l'historique des commits a changé. Si d'autres personnes travaillent sur la même branche, prévenez-les.

0 votes

Pourriez-vous donner un exemple de l'expression "essayer une autre stratégie" ?

0 votes

@pfabri vous pourriez par exemple choisir deux plages de commits où vous laissez de côté le mauvais commit. Vous pourriez revenir sur le mauvais commit. Vous pourriez éviter d'utiliser une solution git en annulant manuellement les modifications, ou en partant d'une nouvelle branche sans le mauvais commit et en refaisant manuellement les bonnes modifications. Si le mauvais commit contient des données sensibles, vous aurez besoin d'une stratégie plus prudente : aide.github.com/fr/articles/

3voto

Jefromi Points 127932

Il semble donc que le mauvais commit ait été incorporé dans un merge commit à un moment donné. Est-ce que votre "merge commit" a déjà été retiré ? Si oui, alors vous voudrez utiliser git revert vous devrez serrer les dents et gérer les conflits. Si ce n'est pas le cas, vous pouvez soit rebaser, soit revenir en arrière, mais vous pouvez le faire de la manière suivante avant le commit de fusion, puis refaire la fusion.

Il n'y a pas beaucoup d'aide que nous pouvons vous donner pour le premier cas, vraiment. Après avoir essayé le retour en arrière, et constaté que le retour automatique a échoué, vous devez examiner les conflits et les résoudre de manière appropriée. C'est exactement le même processus que pour la correction des conflits de fusion ; vous pouvez utiliser git status pour voir où se trouvent les conflits, éditer les fichiers non fusionnés, trouver les fichiers en conflit, trouver comment les résoudre, ajouter les fichiers en conflit, et enfin valider. Si vous utilisez git commit par lui-même (pas de -m <message> ), le message qui s'affiche dans votre éditeur devrait être le modèle de message créé par la commande git revert Vous pouvez ajouter une note expliquant comment vous avez résolu les conflits, puis enregistrer et quitter pour valider.

Pour le second cas, la résolution du problème avant votre fusion, il y a deux sous-cas, selon que vous avez fait plus de travail depuis la fusion. Si vous ne l'avez pas fait, vous pouvez simplement git reset --hard HEAD^ pour supprimer la fusion, faire le revert, puis refaire la fusion. Mais je suppose que vous l'avez fait. Donc, vous allez finir par faire quelque chose comme ça :

  • créer une branche temporaire juste avant la fusion, et la vérifier
  • faire le retour en arrière (ou utiliser git rebase -i <something before the bad commit> <temporary branch> pour supprimer le mauvais commit)
  • refaire la fusion
  • rebasez votre travail ultérieur sur : git rebase --onto <temporary branch> <old merge commit> <real branch>
  • supprimer la branche temporaire

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