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 lorsque je l’ai tiré, soit lorsque j’ai essayé de sélectionner les fichiers spécifiques que je voulais. J’ai cherché et joué pendant longtemps, essayant de comprendre comment supprimer les commits qui contiennent les modifications apscopes à ces fichiers, il semble y avoir un mélange d’inversion et de rebase, et il n’y a pas d’exemples simples, et Je suppose que je sais plus que moi.

Voici donc une version simplifiée de la question:

Compte tenu du scénario suivant, comment puis-je supprimer 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 escompté est

 $ cat myfile line 1 line 3 

Voici un exemple de la façon dont j’ai essayé de revenir

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

L’algorithme utilisé par Git pour calculer les différences à annuler nécessite que

  1. les lignes qui sont annulées ne sont pas modifiées par des commits ultérieurs.
  2. qu’il n’y ait pas d’autres commits “adjacents” plus tard dans l’histoire.

La définition de “adjacente” est basée sur le nombre de lignes par défaut d’un diff context, qui est 3. Donc, si “myfile” a été construit comme ceci:

 $ cat >myfile < 

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 Revert Strategy. Vous pouvez invoquer git comme ceci:

git revert --strategy resolve

et cela devrait faire un meilleur travail pour comprendre ce que vous vouliez dire. Je ne sais pas quelle est la liste des stratégies disponibles et je ne connais pas non plus la définition d’une stratégie.

Il y a quatre façons de le faire:

  • De manière propre, en revenant mais en gardant en mémoire le retour:

     git revert --strategy resolve  
  • De manière rude, ne supprimez que le dernier engagement:

     git reset --soft "HEAD^" 

Remarque: évitez git reset --hard car il git reset --hard également tous les changements de fichiers depuis le dernier commit. Si --soft ne fonctionne pas, essayez plutôt – --mixed ou – --keep .

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

     git rebase -i HEAD~5 

Et si une erreur est commise:

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

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

     git cherry-pick commit-id 
  • Encore une autre alternative:

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

Note: bien sûr, toutes ces modifications sont effectuées localement, vous devez ensuite git push pour appliquer les modifications à la télécommande. Et si votre repository ne veut pas supprimer le commit (“pas d’avance rapide autorisée”, ce qui arrive quand vous voulez supprimer un commit que vous avez déjà poussé), vous pouvez utiliser git push -f pour forcer les modifications.

Note 2: si vous travaillez sur une twig et que vous devez forcer, vous devez absolument éviter git push --force car cela peut écraser les autres twigs (si vous y avez apporté des modifications, même si votre extraction actuelle se trouve sur une autre twig). Préférez toujours spécifier la twig distante lorsque vous forcez : git push --force origin your_branch .

Très facile

git rebase -i HEAD ~ x

(x = no de commits)

lors de l’exécution du fichier notepad s’ouvrira ‘put’ drop en plus de votre commit entrer la description de l'image ici

et c’est tout ce que vous avez fait … il suffit de synchroniser le tableau de bord git et les modifications seront transférées à distance. Si le commit que vous avez déposé était déjà sur la télécommande, vous devrez forcer la mise à jour. Puisque –force est considéré comme dangereux , utilisez git push --force-with-lease .

Votre choix est entre

  1. garder l’erreur et introduire un correctif et
  2. supprimer l’erreur et modifier l’historique.

Vous devez choisir (1) si le changement erroné a été détecté par quelqu’un d’autre et (2) si l’erreur est limitée à une twig privée non poussée.

Git revert est un outil automatisé à faire (1), il crée une nouvelle annulation annulant certains commit précédents. Vous verrez l’erreur et la suppression dans l’historique du projet, mais les personnes qui tirent de votre référentiel ne rencontreront pas de problèmes lors de la mise à jour. Il ne fonctionne pas de manière automatisée dans votre exemple, vous devez donc éditer «myfile» (pour supprimer la ligne 2), git add myfile et git commit à gérer le conflit. Vous allez ensuite vous retrouver avec quatre commits dans votre historique, avec commit 4 qui annule le commit 2.

Si personne ne se soucie de la modification de votre historique, vous pouvez le réécrire et supprimer commit 2 (choix 2). La méthode la plus simple consiste à utiliser git rebase -i 8230fa3 . Cela vous déposera dans un éditeur et vous pouvez choisir de ne pas inclure le commit erroné en supprimant le commit (et en gardant “pick” à côté des autres messages de validation. Lisez les conséquences de cette opération .

Vous pouvez supprimer les commits indésirables avec git rebase . Supposons que vous ayez inclus des commits d’une twig de sujet d’un collègue dans votre twig de sujet, mais que vous décidez plus tard que vous ne voulez pas 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 de rebase interactive. Par exemple

git-rebase-todo

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

Si le rebase n’a pas réussi, supprimez la twig 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 twig de sujet vers une télécommande, vous devrez peut-être forcer la diffusion depuis la modification de l’historique de la validation. Si d’autres travaillent sur la même twig, donnez-leur une idée.

À partir d’autres réponses ici, j’ai été un peu confus avec la façon dont git rebase -i pourrait être utilisé pour supprimer un commit, alors j’espère que c’est correct de noter mon cas de test ici (très similaire à l’OP).

Voici un script bash que vous pouvez coller pour créer un référentiel de test dans le dossier /tmp :

 set -x rm -rf /tmp/myrepo* cd /tmp mkdir myrepo_git cd myrepo_git git init git config user.name me git config user.email [email protected] mkdir folder echo aaaa >> folder/file.txt git add folder/file.txt git commit -m "1st git commit" echo bbbb >> folder/file.txt git add folder/file.txt git commit -m "2nd git commit" echo cccc >> folder/file.txt git add folder/file.txt git commit -m "3rd git commit" echo dddd >> folder/file.txt git add folder/file.txt git commit -m "4th git commit" echo eeee >> folder/file.txt git add folder/file.txt git commit -m "5th git commit" 

A ce stade, nous avons un file.txt avec ces contenus:

 aaaa bbbb cccc dddd eeee 

À ce stade, HEAD est au 5ème commit, HEAD ~ 1 serait le 4ème et HEAD ~ 4 serait le 1er commit (donc HEAD ~ 5 n’existerait pas). Disons que nous voulons supprimer le 3ème commit – nous pouvons émettre cette commande dans le répertoire myrepo_git :

 git rebase -i HEAD~4 

( Notez que git rebase -i HEAD~5 donne “fatal: Besoin d’une seule révision; HEAD amont invalide ~ 5”. ) Un éditeur de texte (voir la capture d’écran de @Dennis ) s’ouvre avec ces contenus:

 pick 5978582 2nd git commit pick 448c212 3rd git commit pick b50213c 4th git commit pick a9c8fa1 5th git commit # Rebase b916e7f..a9c8fa1 onto b916e7f # ... 

Nous obtenons donc tous les commits depuis (mais sans inclure ) notre HEAD demandé ~ 4. Supprimez le pick 448c212 3rd git commit ligne pick 448c212 3rd git commit et enregistrez le fichier; vous obtiendrez cette réponse de git rebase :

 error: could not apply b50213c... 4th git commit When you have resolved this problem run "git rebase --continue". If you would prefer to skip this patch, instead run "git rebase --skip". To check out the original branch and stop rebasing run "git rebase --abort". Could not apply b50213c... 4th git commit 

À ce stade, ouvrez myrepo_git / folder/file.txt dans un éditeur de texte; vous verrez qu’il a été modifié:

 aaaa bbbb <<<<<<< HEAD ======= cccc dddd >>>>>>> b50213c... 4th git commit 

Fondamentalement, git voit que quand HEAD est arrivé à 2nd commit, il y avait du contenu de aaaa + bbbb ; et puis il a un patch de cccc + dddd ajouté qu’il ne sait pas comment append au contenu existant.

Donc, ici, git ne peut pas décider pour vous – c’est vous qui devez prendre une décision: en supprimant le 3ème commit, vous conservez les modifications apscopes (ici, la ligne cccc ) – ou vous ne le faites pas. Si vous ne le faites pas, supprimez simplement les lignes supplémentaires – y compris le folder/file.txt cccc – dans folder/file.txt aide d’un éditeur de texte, pour qu’il ressemble à ceci:

 aaaa bbbb dddd 

… puis enregistrez le folder/file.txt . Vous pouvez maintenant lancer les commandes suivantes dans le répertoire myrepo_git :

 $ nano folder/file.txt # text editor - edit, save $ git rebase --continue folder/file.txt: needs merge You must edit all merge conflicts and then mark them as resolved using git add 

Ah, donc pour marquer que nous avons résolu le conflit, nous devons git add le folder/file.txt , avant de faire git rebase --continue :

 $ git add folder/file.txt $ git rebase --continue 

Ici, un éditeur de texte s’ouvre à nouveau, montrant la ligne 4th git commit – ici, nous avons la possibilité de modifier le message de validation (qui dans ce cas pourrait être modifié de manière significative en 4th (and removed 3rd) commit ou similaire). Disons que vous ne voulez pas – alors il suffit de quitter l’éditeur de texte sans enregistrer; Une fois que vous faites cela, vous obtiendrez:

 $ git rebase --continue [detached HEAD b8275fc] 4th git commit 1 file changed, 1 insertion(+) Successfully rebased and updated refs/heads/master. 

A ce stade, vous avez maintenant un historique comme celui-ci (que vous pouvez également inspecter avec say gitk . ou d’autres outils) du contenu de folder/file.txt (avec, apparemment, les horodatages inchangés des validations d’origine):

 1st git commit | +aaaa ---------------------------------------------- 2nd git commit | aaaa | +bbbb ---------------------------------------------- 4th git commit | aaaa | bbbb | +dddd ---------------------------------------------- 5th git commit | aaaa | bbbb | dddd | +eeee 

Et si précédemment, nous avons décidé de garder la ligne cccc (le contenu du 3ème commit de git que nous avons supprimé), nous aurions eu:

 1st git commit | +aaaa ---------------------------------------------- 2nd git commit | aaaa | +bbbb ---------------------------------------------- 4th git commit | aaaa | bbbb | +cccc | +dddd ---------------------------------------------- 5th git commit | aaaa | bbbb | cccc | dddd | +eeee 

Eh bien, c’était le genre de lecture que j’espérais avoir trouvé, pour commencer à comprendre comment fonctionne git rebase en termes de suppression des commits / révisions; alors j’espère que cela pourrait aider les autres aussi …

Veuillez suivre ci-dessous la séquence des actions. Puisque nous utilisons –force, vous devez disposer des droits d’administrateur sur le repository git pour ce faire.

Étape 1: Trouvez le commit avant la validation que vous voulez supprimer

Étape 2: Vérification de la validation de git checkout

Étape 3: Créer une nouvelle twig en utilisant votre commande de validation actuelle git checkout -b

Étape 4: Maintenant, vous devez append le commit après le commit supprimé git cherry-pick

Étape 5: Répétez maintenant l’étape 4 pour tous les autres commits que vous souhaitez conserver.

Étape 6: Une fois que tous les commits ont été ajoutés à votre nouvelle succursale et ont été validés. Vérifiez que tout est dans le bon état et fonctionne comme prévu. Double check tout a été validé: git status

Étape 7: Basculez vers votre twig cassée git checkout

Étape 8: Maintenant, effectuez une réinitialisation matérielle de la twig cassée à la validation avant celle que vous souhaitez supprimer. git reset --hard

Étape 9: Fusionnez votre twig fixe dans cette twig git merge

Étape 10: Ramenez les modifications fusionnées à leur origine. ATTENTION: Cela va écraser le repo distant! git push --force origin

Vous pouvez effectuer le processus sans créer de nouvelle twig en remplaçant les étapes 2 et 3 par l’étape 8, puis ne pas exécuter les étapes 7 et 9.

Il semble donc que le mauvais commit a été incorporé dans un commit de fusion à un moment donné. Votre validation de fusion a-t-elle déjà été retirée? Si oui, alors vous voudrez utiliser git revert ; Vous devrez serrer les dents et surmonter les conflits. Si non, alors vous pourriez peut-être soit rebaser ou revenir en arrière, mais vous pouvez le faire avant la validation de la fusion, puis refaire la fusion.

Nous ne pouvons vraiment pas vous aider pour le premier cas. Après avoir essayé le retour en arrière et constaté que l’automatique a échoué, vous devez examiner les conflits et les corriger de manière appropriée. C’est exactement le même processus que la résolution des conflits de fusion; vous pouvez utiliser l’ git status pour voir où sont les conflits, éditer les fichiers non fusionnés, trouver les composants conflictuels, déterminer comment les résoudre, append les fichiers en conflit et enfin valider. Si vous utilisez git commit par lui-même (no -m ), le message qui apparaît dans votre éditeur doit être le message de modèle créé par git revert ; vous pouvez append une note sur la façon dont vous avez résolu les conflits, puis enregistrer et quitter pour valider.

Pour le second cas, en résolvant le problème avant votre fusion, il existe 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 annuler la fusion, effectuer le retour, puis refaire la fusion. Mais je suppose que tu as. Donc, vous finirez par faire quelque chose comme ça:

  • créer une twig temporaire juste avant la fusion et la vérifier
  • faire le retour (ou utiliser git rebase -i pour supprimer le mauvais commit)
  • refaire la fusion
  • rebassez votre travail suivant sur: git rebase --onto
  • enlever la twig temporaire

Donc, vous avez fait du travail et poussé, vous pouvez les appeler commet A et B. Votre collègue a également travaillé, valide C et D. Vous avez fusionné vos collègues de travail dans le vôtre (fusion de commit E), puis continué à travailler, aussi (commit F), et a découvert que votre collègue a changé certaines choses qu’il ne devrait pas avoir.

Ainsi, votre historique de validation ressemble à ceci:

 A -- B -- C -- D -- D' -- E -- F 

Vous voulez vraiment vous débarrasser de C, D et D ‘. Puisque vous dites que vous avez fusionné vos collègues de travail dans le vôtre, ces commits sont déjà “disponibles”, donc supprimer les commits en utilisant, par exemple, git rebase est un non-non. Croyez-moi, j’ai essayé.

Maintenant, je vois deux sorties:

  • Si vous n’avez pas encore envoyé E et F à votre collègue ou à quelqu’un d’autre (généralement votre serveur d’origine), vous pouvez toujours supprimer ceux de l’historique pour le moment. C’est votre travail que vous souhaitez enregistrer. Cela peut être fait avec un

     git reset D' 

    (remplacez D ‘par le hachage de validation que vous pouvez obtenir dans un git log

    À ce stade, les validations E et F ont disparu et les modifications sont à nouveau des modifications non validées dans votre espace de travail local. À ce stade, je les déplacerais dans une twig ou les transformerais en patch et l’enregistrerais plus tard. Maintenant, retournez le travail de votre collègue, soit automatiquement avec un git revert automatique ou manuellement. Lorsque vous avez fait cela, relisez votre travail en plus. Vous pouvez avoir des conflits de fusion, mais au moins ils seront dans le code que vous avez écrit, au lieu du code de votre collègue.

  • Si vous avez déjà poussé le travail que vous avez fait après les commits de votre collègue, vous pouvez toujours essayer d’obtenir un “patch inversé” manuellement ou en utilisant git revert , mais puisque votre travail est “sur le point” probablement plus de conflits de fusion et plus de confusion. On dirait que c’est ce que vous avez fini dans …