web-dev-qa-db-fra.com

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 dans le mien, soit lorsque je l'ai extrait, soit lorsque j'ai simplement essayé de sélectionner les fichiers que je voulais. Je cherche et joue depuis longtemps, essayant de trouver comment supprimer les commits contenant les modifications apportées à ces fichiers. Les médecins supposent que j'en sais plus que moi.

Voici donc une version simplifiée de la question:

Dans le scénario suivant, comment 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 comment j'ai essayé de revenir

$ 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.
243
Joshua Cheek

L’algorithme utilisé par Git lors du calcul des diff à récupérer nécessite que

  1. les lignes en cours de retour ne sont modifiées par aucun commet ultérieur.
  2. qu'il n'y ait pas d'autres commises "adjacentes" plus tard dans l'histoire.

La définition de "adjacent" est basée sur le nombre de lignes par défaut d'un diff de contexte, qui est 3. Donc si "myfile" a été 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é publiée officiellement (bien qu’elle soit disponible dans Git v1.7.2-rc2) appelée Stratégie de retour. Vous pouvez invoquer git comme ceci:

git revert - resolution de la stratégie <commit>

et il devrait faire un meilleur travail pour comprendre ce que vous vouliez dire. Je ne connais pas la liste des stratégies disponibles, pas plus que la définition d’une stratégie.

58
Hal Eisen

Il y a quatre façons de le faire:

  • Propre façon, revenir mais garder dans le journal le retour:

    git revert --strategy resolve <commit>
    
  • Harsh way, supprimez au total seulement le dernier commit:

    git reset --soft "HEAD^"
    

Remarque: évitez git reset --hard car cela annule également toutes les modifications apportées aux fichiers depuis la dernière validation. Si --soft ne fonctionne pas, essayez plutôt --mixed ou --keep.

  • Rebase (affiche le journal des 5 derniers commits et supprime les lignes que tu ne veux pas, ou réorganise, ou écrase plusieurs commits en un, ou fais ce que tu veux, 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 identifiant:

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

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

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

Remarque: bien sûr, toutes ces modifications sont effectuées localement, vous devez ensuite git Push appliquer ces modifications à la télécommande. Et au cas où votre référentiel ne souhaite pas supprimer la validation ("pas d'avance rapide autorisée", ce qui se produit lorsque vous souhaitez supprimer une validation que vous avez déjà poussée), vous pouvez utiliser git Push -f pour forcer l'application des modifications.

Note2: si vous travaillez sur une branche et que vous devez forcer Push, vous devez absolument éviter git Push --force car cela pourrait écraser d'autres branches (si vous les avez modifiées, même si votre commande actuelle se trouve sur une autre branche). Préférez à toujours spécifier la branche distante lorsque vous forcez Push : git Push --force Origin your_branch.

246
gaborous

Moyen très facile

git rebase -i HEAD ~ x

(x = nombre de commits)

lors de l'exécution du fichier bloc-notes va ouvrir 'mettre' déposer à côté de votre commit enter image description here

et c'est tout ce que vous avez fait ... synchronisez simplement le tableau de bord git et les modifications seront transmises à distance. Si le commit que vous avez supprimé était déjà sur la télécommande, vous devrez forcer la mise à jour. Puisque --force est considéré nuisible , utilisez git Push --force-with-lease.

80
JD-V

Votre choix est entre

  1. garder l'erreur et introduire un correctif et
  2. supprimer l'erreur et changer l'historique.

Vous devez choisir (1) si le changement erroné a été relevé 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é à 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 extraient de votre référentiel ne rencontreront aucun problème lors de leur mise à jour. Cela ne fonctionne pas de manière automatisée dans votre exemple, vous devez donc éditer 'monfichier' (pour supprimer la ligne 2), faites git add myfile et git commit pour régler le conflit. Vous vous retrouverez alors avec quatre commits dans votre historique, avec commit 4, annulant commit 2.

Si personne ne se soucie que votre historique change, vous pouvez le réécrire et supprimer le commit 2 (choix 2). Pour ce faire, utilisez git rebase -i 8230fa3. Cela vous laissera tomber dans un éditeur et vous pouvez choisir de ne pas inclure la validation erronée en supprimant la validation (et en conservant l'option "pick" à côté des autres messages de validation. Consultez la conséquences de cette opération .

36
Andrew Walker

Approch 1

Commencez par obtenir le hachage de validation (ex: 1406cd61) que vous devez restaurer. solution simple sera en dessous de la commande,

$ git revert 1406cd61

si vous avez validé plus de modifications liées aux fichiers 1406cd61 après la validation 1406cd61, la commande simple ci-dessus ne fonctionnera pas. Ensuite, vous devez suivre les étapes ci-dessous, qui consistent à choisir les cerises.

Approch 2

Veuillez suivre les actions ci-dessous. Comme nous utilisons --force, vous devez disposer de droits d’administrateur sur le dépôt git pour le faire.

Étape 1: Trouvez le commit avant le commit que vous souhaitez supprimer git log

Étape 2: Checkout qui valide git checkout <commit hash>

Étape 3: Créez une nouvelle branche en utilisant votre validation de validation actuelle git checkout -b <new branch>

Étape 4: Maintenant, vous devez ajouter le commit après le commit supprimé git cherry-pick <commit hash>

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

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

Étape 7: Basculez vers votre branche endommagée git checkout <broken branch>

Étape 8: Maintenant, effectuez une réinitialisation matérielle sur la branche cassée du commit avant celui que vous souhaitez supprimer git reset --hard <commit hash>

Étape 9: Fusionner votre branche fixe dans cette branche git merge <branch name>

Étape 10: Renvoyez les modifications fusionnées à l'origine. AVERTISSEMENT: cela écrasera le dépôt distant! git Push --force Origin <branch name>

Vous pouvez effectuer le processus sans créer de nouvelle branche en remplaçant les étapes 2 et 3 par les étapes 8 et non les étapes 7 et 9.

22
tk_

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

git-rebase-todo

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

Si la réinitialisation a échoué, 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 de sujet sur une télécommande, vous devrez peut-être forcer le Push car l'historique de validation a changé. Si d'autres travaillent sur la même branche, prévenez-les.

17
Dennis

D’autres réponses ici, j’étais un peu confus avec la façon dont git rebase -i pourrait être utilisé pour supprimer un commit, j’espère donc que c’est bien de noter mon scénario de test ici (très similaire au PO).

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"

À ce stade, nous avons un file.txt avec le contenu suivant:

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 résulte avec "fatal: Nécessite une seule révision; HEAD ~ 5 en amont non valide". ) Un éditeur de texte (voir capture d'écran dans @ Dennis 'answer ) s'ouvrira avec le contenu suivant:

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 ne comprenant pas ) notre HEAD demandée ~ 4. Supprimez la 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 lorsque HEAD est arrivé à la 2e validation, il y avait un contenu de aaaa + bbbb; et puis il a un correctif de cccc + dddd qu’il ne sait pas comment ajouter au contenu existant.

Donc ici git ne peut pas décider pour vous - c'est vous qui devez prendre la décision: en supprimant le 3ème commit, vous devez soit conserver le changements introduits par celle-ci (ici, la ligne cccc) - ou non. Si vous ne le faites pas, supprimez simplement les lignes supplémentaires - y compris la cccc - dans folder/file.txt à l'aide d'un éditeur de texte, afin qu'il ressemble à ceci:

aaaa
bbbb
dddd

... puis enregistrez folder/file.txt. Vous pouvez maintenant exécuter 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 et affiche la ligne 4th git commit. Vous avez ici 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 quittez simplement l'éditeur de texte sans sauvegarder; Une fois que vous faites cela, vous aurez:

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

À ce stade, vous avez maintenant une histoire comme celle-ci (que vous pouvez également inspecter avec le code gitk . ou d’autres outils) du contenu de folder/file.txt (avec, apparemment, l’horodatage des commits originaux):

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

Et si auparavant, nous avions décidé de conserver la ligne cccc (le contenu du 3ème git commit 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 git rebase fonctionne en termes de suppression de validations/révisions; alors espérons que cela pourrait aider les autres aussi ...

7
sdaau

On dirait donc que le mauvais commit a été incorporé dans un commit de fusion à un moment donné. Votre commit de fusion a-t-il déjà été retiré? Si oui, alors vous voudrez utiliser git revert; vous devrez serrer les dents et résoudre les conflits. Si non, alors vous pourriez peut-être rebaser ou rétablir, mais vous pouvez le faire avant la validation de la fusion, puis rétablir la fusion.

Nous ne pouvons vraiment pas vous aider pour le premier cas. Après avoir essayé la restauration et constaté l'échec de la tentative automatique, vous devez examiner les conflits et les résoudre correctement. Il s’agit exactement du même processus que la résolution des conflits de fusion; vous pouvez utiliser git status pour voir où se trouvent les conflits, éditer les fichiers non fusionnés, rechercher les morceaux en conflit, déterminer comment les résoudre, ajouter les fichiers en conflit et enfin valider. Si vous utilisez git commit seul (pas de -m <message>), le message qui apparaît dans votre éditeur doit être le modèle de message créé par git revert; vous pouvez ajouter une note sur la façon dont vous avez résolu les conflits, puis enregistrez et quittez pour commettre.

Pour le second cas, résoudre le problème avant votre fusion, il existe deux sous-cas, selon que vous avez effectué davantage de travail depuis la fusion. Si vous ne l'avez pas déjà fait, vous pouvez simplement git reset --hard HEAD^ pour annuler la fusion, effectuer la restauration, puis rétablir l'opération. Mais je suppose que vous avez. Donc, vous finirez par faire quelque chose comme ça:

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

Donc, vous avez travaillé et poussé, appelons-les commits A et B. Votre collègue a également travaillé, engagé C et D. Vous avez fusionné votre travail avec le vôtre (fusionnement E), puis poursuivi votre travail, engagé, aussi (commit F), et a découvert que votre collègue avait changé certaines choses qu’il n’aurait pas dû.

Donc, votre historique de commit 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à "diffusés", aussi supprimez-vous en utilisant, par exemple, des commits. Git Rebase est un non-non. Croyez-moi, j'ai essayé.

Maintenant, je vois deux issues:

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

    git reset D'
    

    (remplacez D par le hachage de validation actuel que vous pouvez obtenir à partir d'un git log

    À ce stade, les validations E et F ont disparu et les modifications sont à nouveau non validées dans votre espace de travail local. À ce stade, je les déplacerais dans une branche ou les transformerais en correctif et le sauvegarder pour plus tard. Maintenant, revenez au travail de votre collègue, soit automatiquement avec un git revert, soit manuellement. Lorsque vous avez terminé, répétez votre travail. Vous pouvez avoir des conflits de fusion, mais au moins ils seront dans le code vous écrit, au lieu du code de votre collègue.

  • Si vous avez déjà poussé le travail que vous avez fait après que votre collègue se soit engagé, vous pouvez toujours essayer d'obtenir un "patch inversé" manuellement ou en utilisant git revert, mais puisque votre travail est "sur le chemin", pour ainsi dire vous aurez probablement plus de conflits de fusion et plus de confusion. On dirait que c'est ce que tu as fini dans ...

1
Frans