44 votes

Comment annuler un commit git --amend ?

J'ai accidentellement tapé dans un git commit --amend. C'est une erreur parce que j'ai réalisé que le commit est en fait entièrement nouveau et qu'il devrait être committé avec un nouveau message. Je veux faire un nouveau commit. Comment annuler cette erreur ?

112voto

torek Points 25463

Commentaire de PetSerAl est la clé. Voici la séquence de deux commandes pour faire exactement ce que vous voulez :

git reset --soft @{1}
git commit -C @{1}

et une explication de son fonctionnement.

Description

Lorsque vous faites un nouveau commit, Git utilise généralement 1 utilise cette séquence d'événements :

  1. Lire l'ID (hachage SHA-1, comme a123456... ) du commit en cours (via HEAD qui nous donne la branche actuelle). Appelons cet ID C (pour Current). Notez que ce commit actuel a un commit parent ; appelons son ID P (pour les parents).
  2. Transformez l'index (aka staging-area) en arbre. Cela produit un autre ID ; appelons cet ID T (pour l'arbre).
  3. Ecrire un nouveau commit avec parent = C et arbre = T . Ce nouveau commit obtient un autre ID. Appelons-le N (pour Nouveau).
  4. Mettre à jour la branche avec le nouvel ID de commit N .

Lorsque vous utilisez --amend Git change un peu le processus. Il écrit toujours un nouveau commit comme avant, mais à l'étape 3, au lieu d'écrire le nouveau commit avec parent = C il l'écrit avec parent = P .

Photo

De manière imagée, nous pouvons dessiner ce qui s'est passé de cette façon. Nous commençons avec un graphe de livraison qui se termine par P--C pointé du doigt par branch :

...--P--C   <-- branch

Quand on fait le nouveau commit N on obtient :

...--P--C--N   <-- branch

Lorsque nous utilisons --amend nous obtenons ceci à la place :

       C
      /
...--P--N   <-- branch

Notez que commit C est toujours dans le référentiel il a juste été mis de côté, hors du chemin, pour que le nouveau commit N peut pointer vers l'ancien parent P .

Objectif

Ce que vous avez réalisé veulent après le git commit --amend est de faire en sorte que la chaîne ressemble plutôt à ceci :

...--P--C--N   <-- branch

On ne peut pas tout à fait faire cela - nous ne pouvons pas changer N ; Git ne peut jamais changer tout (ou tout autre objet) une fois qu'il est stocké dans le dépôt - mais notez que la fonction ...--P--C La chaîne est toujours là, intacte. Vous pouvez trouver l'engagement C à travers les reflogs, et c'est ce que le @{1} syntaxe le fait. (Plus précisément, il s'agit de l'abréviation de _currentbranch_@{1} , 2 ce qui signifie "où currentbranch a fait un pas en avant", qui était "d'engager C ".)

Donc, nous exécutons maintenant git reset --soft @{1} qui fait cela :

       C   <-- branch
      /
...--P--N

Maintenant branch Les points suivants C qui renvoie à P .

Qu'arrive-t-il à N ? La même chose qui est arrivée à C avant : il est sauvegardé pour un moment par le reflog.

Nous n'en avons pas vraiment besoin (bien qu'il puisse s'avérer utile), car les --soft pour git reset garde l'index / la zone de mise en scène intacte (ainsi que l'arbre de travail). Cela signifie que nous pouvons refaire un nouveau commit maintenant, en lançant simplement une autre commande git commit . Il passera par les quatre mêmes étapes (lire l'ID de HEAD , créer l'arbre, créer un nouveau commit, et mettre à jour la branche) :

       C--N2   <-- branch
      /
...--P--N

N2 sera notre nouveau nouveau (deuxième nouveau ?) commit.

Nous pouvons même faire git commit réutiliser le message de commit de commit N . Le site git commit La commande a un --reuse-message argument, également orthographié -C Tout ce qu'il faut faire, c'est lui donner quelque chose qui lui permette de trouver le nouveau commit original. N à partir duquel copier le message, pour faire N2 avec. Comment faisons-nous cela ? La réponse est : c'est dans le reflog, tout comme C c'était quand nous devions faire le git reset .

En fait, c'est le même @{1} !

Rappelez-vous, @{1} signifie "là où il était il y a un instant", et git reset Je viens de le mettre à jour, en le déplaçant de C à N . Nous n'avons pas encore sur nouvel engagement N2 . (Une fois que nous l'aurons fait, N sera @{2} mais nous ne l'avons pas encore fait).

Donc, en mettant tout ça ensemble, on obtient :

git reset --soft @{1}
git commit -C @{1}

1 Les endroits où cette description tombe en panne sont notamment lorsque vous modifiez une fusion, lorsque vous êtes sur un HEAD détaché, et lorsque vous utilisez un index alternatif. Même dans ces cas, la façon de modifier la description est assez évidente.

2 Si HEAD est détaché, de sorte qu'il y a est sans branche actuelle, la signification devient HEAD@{1} . Notez que @ par lui-même est l'abréviation de HEAD donc le fait que @{_n_} fait référence à la branche actuelle, plutôt qu'à HEAD lui-même, est un peu incohérent.

Pour voir comment ils diffèrent, considérez git checkout develop suivi par git checkout master (en supposant que les deux branches existent). La première checkout changements HEAD pour pointer vers develop et le deuxième changement HEAD pour pointer vers master . Cela signifie que master@{1} c'est n'importe quel engagement master pointée, avant la dernière mise à jour de master mais HEAD@{1} est l'engagement develop pointe vers maintenant - probablement un autre commit.

(Récapitulation : après ces deux git checkout des commandes, @{1} signifie master@{1} maintenant, HEAD@{1} signifie le même engagement que develop maintenant, et @ signifie HEAD . Si vous êtes confus, eh bien, je l'étais aussi, et apparemment je ne suis pas le seul : voir les commentaires).

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