Comment supprimer une révision spécifique dans l'historique git?


213

Supposons que votre historique git ressemble à ceci:

1 2 3 4 5

1 à 5 sont des révisions distinctes. Vous devez supprimer 3 tout en conservant 1, 2, 4 et 5. Comment procéder?

Existe-t-il une méthode efficace lorsqu'il y a des centaines de révisions après celle à supprimer?


Cette question est mal définie. Il ne dit pas explicitement que l'auteur veut 1-2- (3 + 4) -5 ou 1-2-4-5
RandyTek

14
Eh bien 8 ans plus tard, je ne peux pas vous dire exactement quel problème j'essayais de résoudre. Mais dans git, il y a toujours beaucoup de façons de faire quelque chose, et il y a beaucoup de réponses que diverses personnes ont aimées, donc je suppose que l'ambiguïté ne cause pas trop de difficultés à beaucoup de gens
1800 INFORMATION

1
git rebase --onto 2 3 HEADsignifie à peu près rebaser sur 2, avec des commits entre 3 et HEAD (HEAD est facultatif, ou 5 dans ce cas)
neaumusic

Réponses:


76

Pour combiner les révisions 3 et 4 en une seule révision, vous pouvez utiliser git rebase. Si vous souhaitez supprimer les modifications de la révision 3, vous devez utiliser la commande d'édition en mode de rebase interactif. Si vous souhaitez combiner les modifications en une seule révision, utilisez squash.

J'ai utilisé avec succès cette technique de squash, mais je n'ai jamais eu besoin de supprimer une révision auparavant. La documentation de git-rebase sous "Splitting commits" devrait, espérons-le, vous donner suffisamment d'idée pour le comprendre. (Ou quelqu'un d'autre pourrait le savoir).

De la documentation git :

Démarrez-le avec le plus ancien commit que vous souhaitez conserver tel quel:

git rebase -i <after-this-commit>

Un éditeur sera lancé avec toutes les validations de votre branche actuelle (en ignorant les validations de fusion), qui surviennent après la validation donnée. Vous pouvez réorganiser les validations de cette liste au contenu de votre cœur et vous pouvez les supprimer. La liste ressemble plus ou moins à ceci:

pick deadbee La ligne de ce commit
pick fa1afe1 La ligne du prochain commit
...

Les descriptions en ligne sont purement pour votre plaisir; git-rebase ne les regardera pas mais les noms de commit ("deadbee" et "fa1afe1" dans cet exemple), donc ne supprimez pas et ne modifiez pas les noms.

En remplaçant la commande "pick" par la commande "edit", vous pouvez dire à git-rebase de s'arrêter après avoir appliqué ce commit, afin de pouvoir éditer les fichiers et / ou le message de commit, modifier le commit et continuer le rebasage.

Si vous souhaitez plier deux ou plusieurs validations en une seule, remplacez la commande "pick" par "squash" pour la deuxième validation et les suivantes. Si les commits avaient des auteurs différents, il attribuera le commit écrasé à l'auteur du premier commit.


42
-1 La question est bien définie mais cette réponse n'est pas si claire. L'auteur ne dit pas quelle est la solution exacte.
Aleksandr Levchuk

1
C'est une mauvaise piste. La section SPLITTING COMMITS n'est pas la bonne. Vous voulez lire beaucoup plus haut dans le manuel - voir la réponse de @Rares Vernica.
Aleksandr Levchuk

1
@AleksandrLevchuk La question n'est pas bien définie: la façon dont la question est énoncée ne permet pas de savoir si l'ensemble de modifications de 3 doit être conservé ou rejeté. Je conviens que si les modifications doivent être rejetées, les autres réponses offrent une approche plus facile. Cependant, si les modifications devaient être conservées, il s'agirait d'une opération purement esthétique. Les deux approches réécrivent l'histoire d'une manière dangereuse si d'autres peuvent avoir basé leur travail sur une histoire défectueuse; si tel est le cas, le nettoyage cosmétique ne devrait pas être effectué, et la suppression des modifications serait mieux effectuée via git revert.
Theodore Murdock

125

D'après ce commentaire (et j'ai vérifié que c'est vrai), la réponse de rado est très proche mais laisse git dans un état de tête détaché. Au lieu de cela, supprimez-le HEADet utilisez-le pour supprimer <commit-id>de la branche sur laquelle vous vous trouvez:

git rebase --onto <commit-id>^ <commit-id>

1
Si vous pouviez me dire comment supprimer également ce commit-id de l'historique basé sur juste commit-id, vous seriez mon héros.
kayleeFrye_onDeck

Pouvez-vous s'il vous plaît inclure une explication de ce que la commande magique fait dans la réponse? C'est-à-dire ce que chaque paramètre dénote?
alexandroid

c'est génial mais que faire si je veux supprimer le tout premier commit de l'histoire? (c'est pourquoi je suis venu ici: P)
Sterling Camden

2
Pour une raison quelconque, rien ne se passe lorsque je lance ceci. Cependant, changer ^pour le ~1faire fonctionner.
Antimony

Bien en retard, mais je vais ajouter que si vous rencontrez des conflits de fusion, abandonnez le rebase, puis réexécutez- --strategy-option theirsle à la fin.
LastStar007

122

Voici un moyen de supprimer de manière non interactive un élément spécifique <commit-id>, en ne connaissant que les éléments que <commit-id>vous souhaitez supprimer:

git rebase --onto <commit-id>^ <commit-id> HEAD

2
A aussi fonctionné pour moi. Btw, que fait l'opérateur ^? Cela signifie-t-il le prochain commit après celui spécifié?
hopia

3
@hopia signifie le (premier) parent du commit spécifié. Voir "git help revisions"
Emil Styrke

12
Voir la recommandation de @ kareem dans sa réponse d'omettre HEADafin d'éviter une tête détachée.
mklement0

1
Beaucoup plus facile que d'avoir à déterminer le nombre de validations de recherche.
Dana Woodman

1
c'est génial mais que faire si je veux supprimer le tout premier commit de l'histoire? (c'est pourquoi je suis venu ici: P)
Sterling Camden

76

Comme indiqué précédemment, git-rebase (1) est votre ami. En supposant que les commits sont dans votre masterbranche, vous feriez:

git rebase --onto master~3 master~2 master

Avant:

1---2---3---4---5  master

Après:

1---2---4'---5' master

Depuis git-rebase (1):

Une gamme de validations peut également être supprimée avec rebase. Si nous avons la situation suivante:

E---F---G---H---I---J  topicA

puis la commande

git rebase --onto topicA~5 topicA~3 topicA

entraînerait la suppression des commits F et G:

E---H'---I'---J'  topicA

Ceci est utile si F et G étaient défectueux d'une manière ou d'une autre ou ne devraient pas faire partie du sujet A. Notez que l'argument de --onto et le paramètre peuvent être n'importe quel commit-ish valide.


3
ne devrait-il pas en être ainsi --onto master~3 master~1?
Matthias

3
Si vous voulez simplement supprimer le dernier commit, c'est --onto master ~ 1 master
MikeHoss

Je voudrais porter ce sol. mais je reçois l' MERGE CONFLICTerreur. J'ai utilisé le scénario mentionné dans stackoverflow.com/questions/2938301/remove-specific-commit et je ne parviens pas à supprimer le 2e commit dans cet exemple.
maan81

22

Si tout ce que vous voulez faire est de supprimer les modifications apportées dans la révision 3, vous pouvez utiliser git revert.

Git revert crée simplement une nouvelle révision avec des modifications qui annulent toutes les modifications de la révision que vous rétablissez.

Cela signifie que vous conservez des informations sur la validation indésirable et la validation qui supprime ces modifications.

C'est probablement beaucoup plus convivial s'il est possible que quelqu'un se soit retiré de votre référentiel entre-temps, car le retour est fondamentalement juste un commit standard.


4
Malheureusement, ce n'est pas une bonne solution pour moi car quelqu'un a accidentellement commis 100 Mo de merde au dépôt, faisant exploser la taille et rendant l'interface Web lente.
Stephen Smith

18

Jusqu'à présent, toutes les réponses ne répondent pas à la préoccupation finale:

Existe-t-il une méthode efficace lorsqu'il y a des centaines de révisions après celle à supprimer?

Les étapes suivent, mais pour référence, supposons l'historique suivant:

[master] -> [hundreds-of-commits-including-merges] -> [C] -> [R] -> [B]

C : commit juste après le commit à supprimer (clean)

R : le commit à supprimer

B : commit juste avant le commit à supprimer (base)

En raison de la contrainte «des centaines de révisions», je suppose les conditions préalables suivantes:

  1. il y a un engagement embarrassant que vous souhaiteriez ne jamais avoir existé
  2. il y a ZERO commits ultérieurs qui dépendent réellement de ce commit embarrassant (zéro conflit lors du retour)
  3. vous ne vous souciez pas que vous serez répertorié comme le «Committer» des centaines de commits intermédiaires («Author» sera conservé)
  4. vous n'avez jamais partagé le référentiel
    • ou vous avez en fait suffisamment d'influence sur toutes les personnes qui ont déjà cloné l'histoire avec cet engagement pour les convaincre d'utiliser votre nouvelle histoire
    • et vous ne vous souciez pas de réécrire l'histoire

Il s'agit d'un ensemble de contraintes assez restrictif, mais il existe une réponse intéressante qui fonctionne réellement dans ce cas d'angle.

Voici les étapes:

  1. git branch base B
  2. git branch remove-me R
  3. git branch save
  4. git rebase --preserve-merges --onto base remove-me

S'il n'y a vraiment pas de conflits, cela devrait se poursuivre sans autres interruptions. S'il y a des conflits, vous pouvez les résoudre et rebase --continueou décider de vivre avec l'embarras et rebase --abort.

Maintenant, vous devriez être sur masterqui n'a plus validé R dedans. La savebranche indique où vous étiez avant, au cas où vous souhaiteriez vous réconcilier.

La façon dont vous souhaitez organiser le transfert de tous les autres vers votre nouvel historique dépend de vous. Vous aurez besoin de connaître stash, reset --hardet cherry-pick. Et vous pouvez supprimer les base, remove-meet les savebranches


comment puis-je aimer ce 150000 fois?
Gena Moroz

A travaillé pour moi pour supprimer 3 commits en séquence du milieu de l'histoire d'une branche où quelqu'un a commis un tas de fichiers de 150 Mo.
Marcos

3

J'ai également atterri dans une situation similaire. Utilisez un rebase interactif à l'aide de la commande ci-dessous et tout en sélectionnant, supprimez le 3e commit.

git rebase -i remote/branch

2

Voici donc le scénario auquel j'ai fait face et comment je l'ai résolu.

[branch-a]

[Hundreds of commits] -> [R] -> [I]

voici Rle commit que je devais supprimer, et Ic'est un seul commit qui vient aprèsR

J'ai fait un commit de retour et les ai écrasés ensemble

git revert [commit id of R]
git rebase -i HEAD~3

Lors du squash de rebase interactif, les 2 derniers commits.


0

Les réponses de rado et de kareem ne font rien pour moi (seul le message "La branche actuelle est à jour." Apparaît). Cela peut se produire car le symbole «^» ne fonctionne pas dans la console Windows. Cependant, selon ce commentaire, le remplacement de '^' par '~ 1' résout le problème.

git rebase --onto <commit-id>^ <commit-id>
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.