Comment annuler plusieurs commits git?

984

J'ai un dépôt git qui ressemble à ceci:

A -> B -> C -> D -> HEAD

Je veux que le chef de la branche pointe vers A, c'est-à-dire que je veux que B, C, D et HEAD disparaissent et je veux que head soit synonyme de A.

Il semble que je puisse soit essayer de rebaser (ne s'applique pas, car j'ai poussé les changements entre les deux), soit revenir en arrière. Mais comment annuler plusieurs validations? Dois-je revenir un à la fois? La commande est-elle importante?

Facture
la source
3
Si vous voulez simplement réinitialiser la télécommande, vous pouvez l’encombrer avec n’importe quoi! Mais utilisons le quatrième commit il y a: git push -f HEAD~4:master(en supposant que la branche distante est master). Oui, vous pouvez pousser n'importe quel commit comme ça.
u0b34a0f6ae
21
Si les gens ont tiré, vous devez faire un commit qui annule les modifications en utilisant git revert.
Jakub Narębski
1
Utilisez git show HEAD ~ 4 pour vous assurer que vous
poussez
5
"L'ordre est-il important?" Oui, si les validations affectent les mêmes lignes dans les mêmes fichiers. Ensuite, vous devez commencer à annuler le commit le plus récent et revenir en arrière.
avandeursen

Réponses:

1336

Développer ce que j'ai écrit dans un commentaire

La règle générale est que vous ne devez pas réécrire (modifier) ​​l'historique que vous avez publié, car quelqu'un a peut-être basé son travail dessus. Si vous réécrivez (modifiez) l'historique, vous rencontrez des problèmes avec la fusion de leurs modifications et leur mise à jour.

La solution consiste donc à créer un nouveau commit qui annule les modifications dont vous souhaitez vous débarrasser. Vous pouvez le faire en utilisant la commande git revert .

Vous avez la situation suivante:

A <- B <- C <- D <- master <- HEAD

(les flèches se réfèrent ici à la direction du pointeur: la référence "parent" dans le cas des validations, la validation supérieure dans le cas de la tête de branche (référence de branche) et le nom de la branche dans le cas de la référence HEAD).

Ce que vous devez créer est le suivant:

A <- B <- C <- D <- [(BCD) ^ - 1] <- master <- HEAD

où "[(BCD) ^ - 1]" signifie le commit qui annule les changements dans les commits B, C, D. Les mathématiques nous disent que (BCD) ^ - 1 = D ^ -1 C ^ -1 B ^ -1, donc vous pouvez obtenir la situation requise en utilisant les commandes suivantes:

$ git revert --no-commit D
$ git revert --no-commit C
$ git revert --no-commit B
$ git commit -m "the commit message"

Une autre solution consisterait à extraire le contenu du commit A et à valider cet état:

$ git checkout -f A -- .
$ git commit -a

Vous auriez alors la situation suivante:

A <- B <- C <- D <- A '<- master <- HEAD

Le commit A 'a le même contenu que le commit A, mais est un commit différent (message de commit, parents, date de commit).

La solution de Jeff Ferland, modifiée par Charles Bailey s'appuie sur la même idée, mais utilise git reset :

$ git reset --hard A
$ git reset --soft @{1}  # (or ORIG_HEAD), which is D
$ git commit
Jakub Narębski
la source
40
Si vous avez ajouté des fichiers en B, C ou D. le git checkout -f A -- .ne les supprimera pas, vous devrez le faire manuellement. J'ai appliqué cette stratégie maintenant, merci Jakub
oma
18
Ces solutions ne sont pas équivalentes. Le premier ne supprime pas les fichiers nouvellement créés.
m33lky
10
@Jerry: git checkout foopeut signifier une branche de paiement foo(passer à une branche) ou un fichier de paiement foo (à partir de l'index). --est utilisé pour lever l'ambiguïté, par exemple git checkout -- fooconcerne toujours le fichier.
Jakub Narębski
87
En plus d'une excellente réponse. Ce raccourci fonctionne pour moigit revert --no-commit D C B
welldan97
9
@ welldan97: Merci pour un commentaire. Lors de l'écriture de cette réponse, vous git revertn'avez pas accepté plusieurs validations; c'est un ajout tout à fait nouveau.
Jakub Narębski
248

Voie propre que j'ai trouvée utile

git revert --no-commit HEAD~3..

Cette commande annule les 3 dernières validations avec une seule validation.

Ne réécrit pas non plus l'historique.

plongée profonde
la source
16
Ceci est la meilleure et simple réponse
Julien Deniau
3
@JohnLittle il met en scène les changements. git commità partir de là, fera le commit.
x1a4
14
Cela ne fonctionnera pas si certaines validations sont des validations de fusion.
MegaManX
5
Que font les deux points à la fin?
cardamome
5
@cardamom Ceux-ci spécifient une plage. HEAD~3..est le même queHEAD~3..HEAD
Toine H
238

Pour ce faire, il vous suffit d'utiliser la commande revert , en spécifiant la plage de validations que vous souhaitez annuler.

En tenant compte de votre exemple, vous devriez le faire (en supposant que vous êtes sur la branche «maître»):

git revert master~3..master

Cela va créer un nouveau commit dans votre local avec le commit inverse de B, C et D (ce qui signifie qu'il annulera les changements introduits par ces commits):

A <- B <- C <- D <- BCD' <- HEAD
Victor
la source
129
git revert --no-commit HEAD~2..est une façon un peu plus idiomatique de le faire. Si vous êtes sur la branche master, pas besoin de spécifier à nouveau master. L' --no-commitoption permet à git d'essayer de rétablir toutes les validations à la fois, au lieu de joncher l'historique de plusieurs revert commit ...messages (en supposant que c'est ce que vous voulez).
kubi
6
@Victor J'ai corrigé votre plage de validation. Le début de la gamme est exclusif, ce qui signifie qu'il n'est pas inclus. Donc, si vous souhaitez annuler les 3 dernières validations, vous devez commencer la plage à partir du parent de la 3e validation, c'est-à-dire master~3.
2
@kubi n'existe-t-il aucun moyen d'inclure les SHA dans un message de validation, en utilisant une seule validation (votre méthode mais sans avoir à saisir manuellement les validations qui ont été annulées)?
Chris S
@ChrisS Ma première pensée serait de ne pas utiliser --no-commit(donc vous obtenez un commit séparé pour chaque retour), puis de les écraser tous ensemble dans un rebase interactif. Le message de validation combiné contiendra tous les SHA, et vous pouvez les organiser comme bon vous semble en utilisant votre éditeur de message de validation préféré.
Radon Rosborough
71

Semblable à la réponse de Jakub, cela vous permet de sélectionner facilement des validations consécutives à annuler.

# revert all commits from B to HEAD, inclusively
$ git revert --no-commit B..HEAD  
$ git commit -m 'message'
konyak
la source
9
Votre solution a bien fonctionné pour moi, mais avec une légère modification. Si nous avons ce cas Z -> A -> B -> C -> D -> HEAD et si je veux revenir à l'état A, alors bizarrement je devrais exécuter git revert --no-commit Z .. HEAD
Bogdan
3
D'accord avec @Bogdan, la plage de retour est comme ça: SHA_TO_REVERT_TO..HEAD
Vadym Tyemirov
11
La plage est fausse. Cela devrait être le B^..HEADcas, sinon B est exclu.
tessus
3
D'accord avec @tessus, donc la bonne chose à faire serait: git revert --no-commit B^..HEADougit revert --no-commit A..HEAD
Yoho
64
git reset --hard a
git reset --mixed d
git commit

Cela agira comme un retour pour tous à la fois. Donnez un bon message de validation.

Jeff Ferland
la source
4
S'il veut HEADressembler, Ail veut probablement que l'index corresponde git reset --soft Dest donc probablement plus approprié.
CB Bailey
2
--soft la réinitialisation ne déplace pas l'index, donc lorsqu'il commet, il semblerait que la validation provienne directement d'un au lieu de D. Cela diviserait la branche. --mixed laisse les modifications, mais déplace le pointeur d'index, donc D deviendra le commit parent.
Jeff Ferland, le
5
Oui, je pense que git reset --keep est exactement ce que j'ai ci-dessus. Il est sorti dans la version 1.7.1, sortie en avril 2010, donc la réponse n'était pas là à l'époque.
Jeff Ferland
git checkout Aalors git commitci-dessus n'a pas fonctionné pour moi, mais cette réponse a fonctionné.
SimplGy
Pourquoi est git reset --mixed Drequis? Plus précisément pourquoi reset? Est-ce parce que, sans réinitialiser D, HEAD pointerait A, faisant en sorte que B, C et D "se balancent" et soient récupérés - ce qui n'est pas ce qu'il veut? Mais alors pourquoi --mixed? Vous avez déjà répondu "la --softréinitialisation ne déplace pas l'index ..." Donc, en déplaçant l'index, cela signifie que l'index contiendra les changements de D, tandis que le répertoire de travail contiendra les changements de A - de cette façon a git statusou git diff(qui compare Index [D] à Le répertoire de travail [A]) montrera la substance; cet utilisateur passe de D à A?
The Red Pea
39

Assurez-vous d'abord que votre copie de travail n'est pas modifiée. Alors:

git diff HEAD commit_sha_you_want_to_revert_to | git apply

et puis juste engager. N'oubliez pas de documenter la raison du retour.

mateusz.fiolka
la source
1
A travaillé pour moi! Nous avons eu un problème avec les modifications obsolètes de la branche de fonctionnalité apportées dans la branche de développement (certaines corrections de bogues), de sorte que la branche de fonctionnalité a dû écraser toutes les modifications apportées dans le développement (y compris la suppression de certains fichiers).
silentser
1
Ne fonctionnera pas avec les fichiers binaires:error: cannot apply binary patch to 'some/image.png' without full index line error: some/image.png: patch does not apply
GabLeRoux
2
Il s'agit d'une solution beaucoup plus flexible que la réponse acceptée. Merci!
Brian Kung
2
re: les fichiers binaires utilisent l'option --binary: git diff --binary HEAD commit_sha_you_want_to_revert_to | git apply
weinerk
2
Cela fonctionnera même si vous souhaitez annuler une plage de validations contenant des validations de fusion. Lors de l'utilisation, git revert A..Zvous obtiendrezerror: commit X is a merge but no -m option was given.
Juliusz Gonera
35

Je suis tellement frustré de ne pas pouvoir répondre à cette question. Toutes les autres questions concernent la manière de revenir correctement et de préserver l'histoire. Cette question dit "Je veux que le chef de la branche pointe vers A, c'est-à-dire que je veux que B, C, D et HEAD disparaissent et je veux que head soit synonyme de A."

git checkout <branch_name>
git reset --hard <commit Hash for A>
git push -f

J'ai beaucoup appris en lisant le post de Jakub, mais un gars de l'entreprise (avec accès pour pousser à notre branche "testing" sans Pull-Request) a poussé comme 5 mauvais commits essayant de corriger et de corriger et de corriger une erreur qu'il a faite il y a 5 commits. Non seulement cela, mais une ou deux demandes d'appel ont été acceptées, ce qui était maintenant mauvais. Alors oubliez ça, j'ai trouvé le dernier bon commit (abc1234) et je viens d'exécuter le script de base:

git checkout testing
git reset --hard abc1234
git push -f

J'ai dit aux 5 autres gars travaillant dans ce repo qu'ils feraient mieux de noter leurs changements pour les dernières heures et Wipe / Re-Branch des derniers tests. Fin de l'histoire.

Suamere
la source
2
Je n'avais pas publié les commits, c'était donc la réponse dont j'avais besoin. Merci, @Suamere.
Tom Barron
La meilleure façon de le faire est de git push --force-with-leaseréécrire l'historique uniquement si personne d'autre ne s'est engagé dans la branche après ou dans la plage des validations à vaporiser. Si d'autres personnes ont utilisé la branche, son histoire ne doit jamais être réécrite et le commit doit simplement être visiblement annulé.
frandroid
1
@frandroid "l'histoire ne doit jamais être réécrite", seul l'accord des sith est absolu. La question de ce fil, et le point de ma réponse, est exactement que pour un scénario particulier, toute l'histoire doit être effacée.
Suamere
@Suamere Bien sûr, c'est la question. Mais comme votre réponse mentionne ce que vous aviez à dire aux autres gars, il y a un risque de problème. Par expérience personnelle, push -f peut gâcher votre base de code si d'autres personnes se sont engagées après ce que vous essayez d'effacer. --force-with-lease obtient le même résultat, sauf qu'il vous sauve le cul si vous étiez sur le point de gâcher votre repo. Pourquoi en profiter? Si --force-with-lease échoue, vous pouvez voir quelle validation gêne, évaluer correctement, ajuster et réessayer.
frandroid
1
@Suamere Merci! Je suis d'accord que la question indique clairement qu'elle veut réécrire l'histoire. Je suis dans la même situation que vous et je suppose que l'OP en ce sens que quelqu'un a fait des dizaines de vilains retours et commissions étranges et des retours de retours par accident (pendant que j'étais en vacances) et que l'état doit être rétabli. Dans tous les cas, avec un bon avertissement sain, cela devrait être la réponse acceptée.
Lee Richardson
9

Il s'agit d'une extension d'une des solutions fournies dans la réponse de Jakub

J'étais confronté à une situation où les validations dont j'avais besoin pour revenir en arrière étaient quelque peu complexes, plusieurs des validations étant des validations de fusion, et je devais éviter de réécrire l'historique. Je n'ai pas pu utiliser une série de git revertcommandes car j'ai finalement rencontré des conflits entre les modifications de réversion ajoutées. J'ai fini par utiliser les étapes suivantes.

Tout d'abord, vérifiez le contenu de la validation cible tout en laissant HEAD à la pointe de la branche:

$ git checkout -f <target-commit> -- .

(Le - s'assure <target-commit>est interprété comme un commit plutôt que comme un fichier; le. Fait référence au répertoire courant.)

Ensuite, déterminez quels fichiers ont été ajoutés dans les validations annulées et doivent donc être supprimés:

$ git diff --name-status --cached <target-commit>

Les fichiers qui ont été ajoutés doivent apparaître avec un "A" au début de la ligne, et il ne doit pas y avoir d'autres différences. Maintenant, si des fichiers doivent être supprimés, procédez à leur suppression:

$ git rm <filespec>[ <filespec> ...]

Enfin, validez la réversion:

$ git commit -m 'revert to <target-commit>'

Si vous le souhaitez, assurez-vous que nous sommes de retour à l'état souhaité:

$git diff <target-commit> <current-commit>

Il ne devrait pas y avoir de différences.

Warren Dew
la source
Êtes-vous sûr de pouvoir gitTÊTER avec le bout de la branche?
Suamere
2
C'était une bien meilleure solution pour moi, car dans le mien j'avais des merge commits.
sovemp
3

Le moyen facile de rétablir un groupe de validations sur un référentiel partagé (que les gens utilisent et que vous souhaitez conserver l'historique) est d'utiliser git reverten conjonction avec git rev-list. Ce dernier vous fournira une liste de commits, le premier effectuera le retour lui-même.

Il y a deux façons de procéder. Si vous souhaitez annuler plusieurs validations en une seule utilisation:

for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert -n $i; done

cela annulera un groupe de validations dont vous avez besoin, mais laissez toutes les modifications dans votre arborescence de travail, vous devez toutes les valider comme d'habitude.

Une autre option consiste à avoir un seul commit par modification annulée:

for i in `git rev-list <first-commit-sha>^..<last-commit-sha>`; do git revert --no-edit -s $i; done

Par exemple, si vous avez un arbre de validation comme

 o---o---o---o---o---o--->    
fff eee ddd ccc bbb aaa

pour annuler les modifications de eee à bbb , exécutez

for i in `git rev-list eee^..bbb`; do git revert --no-edit -s $i; done
Ruslan Kabalin
la source
viens de l'utiliser. Merci!
Ran Biron
2

Aucun de ceux-ci ne fonctionnait pour moi, j'ai donc eu trois commits à annuler (les trois derniers commits), alors j'ai fait:

git revert HEAD
git revert HEAD~2
git revert HEAD~4
git rebase -i HEAD~3 # pick, squash, squash

A fonctionné comme un charme :)

Dorian
la source
2
Il s'agit d'une option viable uniquement si vos modifications ne sont pas encore poussées.
kboom
0

À mon avis, un moyen très simple et propre pourrait être:

retourner à A

git checkout -f A

diriger la tête du maître vers l'état actuel

git symbolic-ref HEAD refs/heads/master

enregistrer

git commit
nulll
la source
1
Pouvez-vous expliquer la raison du vote négatif?
nulll
Cela fonctionne bien. Quel est l'intérêt de donner un vote négatif pour cette réponse utile ou quelqu'un peut-il expliquer quelle est la meilleure pratique?
Levent Divilioglu
Est-ce la même chose que git checkout master; git reset --hard A? Sinon, pourriez-vous expliquer un peu plus ce que cela fait?
MM
fonctionne, mais la référence symbolique HEAD ne semble pas être une commande "sûre"
Sérgio
euh je ne veux pas réparer le master, je veux réparer une branche
Sérgio
-6

Si vous souhaitez annuler temporairement les validations d'une fonction, vous pouvez utiliser la série de commandes suivantes.

Voici comment ça marche

git log --pretty = en ligne | grep 'nom_fonction' | coupe -d '' -f1 | xargs -n1 git revert --no-edit

Ajit Singh
la source