(Amélioration de ce dont je parle ici)
Use case typique : on a merdé les derniers commits, et on veut oublier tout ce qu’on a fait et retourner à l’état d’il y a x commits précédents.
Par exemple, là je veux revenir à mon commit 85711ad...
:
commit 093bab5aa9d41f580037d51421b7c5d0db73e2ce Author: samDate: Sat Jun 28 09:37:55 2014 +0700 Ok, c'est la merde internationale commit 0b768f1c0e37a6141e6cd4c472eb3f369f4334d7 Author: sam Date: Sat Jun 28 09:37:36 2014 +0700 Je commence à merder commit 85711ad1e8c54f3fd3048d405addef48921e90fd Author: sam Date: Sat Jun 28 09:37:20 2014 +0700 J'adore ce commit
Il y a plein de manières de faire, et on voit sur la toile beaucoup de solutions à base de checkout
et de reset
. La plupart sont dangereuses ou ont un résultat inattendu, présuppose un état de votre repo ou va vous mettre dans une situation que vous ne maîtrisez pas.
Devinez quoi ? Il y a plus simple, et plus propre.
Etape 1: avoir une copie de travail propre
Avant d’inverser des commits, assurez vous que votre copie de travail est nette. Pas de fichiers modifiés en attente d’être commités. Le moins de fichiers non trackés par git possible (idéalement zéro, soit c’est commité, soit c’est dans le .gitignore).
Si vous avez des fichiers modifiés, vous pouvez soit les mettre de côté temporairement avec git stash, soit annuler toutes les modifications avec git reset --hard HEAD
. Attention, cette dernière commande n’est pas réversible et va mettre à plat votre copie de travail pour qu’elle soit l’exacte copie du dernier commit de votre histo.
Etape 2
???
Etape 3: profit !
git revert --no-commit 85711ad1..HEAD |
Ceci va modifier la copie de travail (donc les fichiers que vous avez sur le disque dur en direct, pas l’histo git) en appliquant des patchs qui contiennent les différences entre HEAD et le commit avec ce hash.
En clair : vos fichiers vont être dans l’état dans lequel ils étaient à ce commit. En prime, l’index est mis à jour.
Vous pouvez alors faire les derniers ajustements que vous le souhaitez. Il faut ensuite finaliser la procédure par un commit avec un message significatif :
git commit -m "Abort ! Abort ! Inversion des 2 derniers commits, retour à 85711a" |
Si vous aviez fait un stash
, c’est le moment de faire un stash apply
derrière.
Maintenant, si vous matez l’histo, vous verrez qu’on n’a pas effacé les commits précédents, on a juste fait un commit qui inverse tout ce qu’ils avaient fait :
commit 03e55de36ad29a26a461874988d4066ebf6fe6be Author: samDate: Sat Jun 28 09:43:32 2014 +0700 Abort ! Abort ! Inversion des 2 derniers commits, retour à 85711a commit 093bab5aa9d41f580037d51421b7c5d0db73e2ce Author: sam Date: Sat Jun 28 09:37:55 2014 +0700 Ok, c'est la merde internationale commit 0b768f1c0e37a6141e6cd4c472eb3f369f4334d7 Author: sam Date: Sat Jun 28 09:37:36 2014 +0700 Je commence à merder commit 85711ad1e8c54f3fd3048d405addef48921e90fd Author: sam Date: Sat Jun 28 09:37:20 2014 +0700 J'adore ce commit
Ce qui évite bien des problèmes : pas de réécriture de l’histo, possibilité de récupérer du code dans les commits inversés plus tard, claire indication de ce qui s’est passé…
N’oubliez pas que souvent, revenir à un commit précédent est overkill. Il est généralement beaucoup plus simple de juste récupérer un ou deux fichiers dans l’état de l’époque avec :
git checkout [hash] -- chemin/vers/fichier |
Pour inverser les derniers commits, il vaut mieux utiliser git rebase -i.
Pour inverser les 2 derniers commits:
#$ git log --oneline -2 # Affiche 2 derniers commits
d3e5d39 Use patch ports to interconnect integration/physical bridges
8df1e96 Increase default metadata_workers, backlog to 4096
#$ git rebase -i HEAD^^ # on arrive dans votre éditeur favori avec les 2 derniers commits
pick d3e5d39 Use patch ports to interconnect integration/physical bridges
pick 8df1e96 Increase default metadata_workers, backlog to 4096
# On échange les 2 lignes
pick 8df1e96 Increase default metadata_workers, backlog to 4096
pick d3e5d39 Use patch ports to interconnect integration/physical bridges
# On sauve et on quitte l'éditeur
#$ git log --oneline -2 # Affiche 2 derniers commits
xxxxxxx Use patch ports to interconnect integration/physical bridges
yyyyyyy Increase default metadata_workers, backlog to 4096
#Les sha1 changent
Félicitation, tu viens juste de réécrire ton historique, t’assurant que ton prochain push ou pulll ne marchera pas si les commits sont publiques, boussillant ta syncro avec les historiques des autres commiters, et rendant difficile la récupération de toute modif des commits qut tu as annulé.
C’est ce que je disais par:
La méthode que tu propose ne marche que si tu n’as jamais pushé ou mergé les dits commits. Si quelqu’un sur le web lis ta méthode et l’utilise sur des commits pushés ou mergés, tu vas le mettre dans une merde pas croyable, et il aura aucune idée de pourquoi.
@ZZelle je pense que ce que Sam soulève comme problème c’est si on a déjà push la branche par exemple. Donc à part si on veut se faire arracher par ses collègues en forçant un push on est obligé de faire ce genre d’opération
Ouais voilà hein. Sinon astuce, plutôt que de faire un git reset –hard HEAD j’aime bien faire un git checkout . ça me fait économiser de la frappe !
@Blef: ouai, mais ça fait que switcher les fichiers changés, il me semble ça del pas ceux qui ne sont pas dans les autres commits.
Génial !
Exactement ce dont j’avais besoin !!
Le terme plus exacte est annuler les derniers commits.
Je trouve que inverser est “confusant” au sens où l’on a l’impression que tu veux réécrire ton historique en changeant l’ordre de tes commits.
Idem, tu devrais mettre “annuler” au lieu de “inverser”. D’ailleurs @ZZelle t’as pris au mot, sa méthode inverse bien les 2 derniers commit
Inverser: Mettre dans le sens contraire.
C’est ici exactement ce que l’on fait.
On avait les commit :
Et on fait un commit de plus, qui fait :
Le fait que le BCA soit fait en un seul commit ne doit pas vous tromper. On appliquer bien exactement l’inverse de la série de commit précédents.
Effectivement, on annule bien les dernier commits, dans le sens supprime leurs effets. On les annule en plaçant une série de modifications parfaitement symétriques : l’inverse des commits précédents.
La raison pour laquelle j’ai choisi inverser et non annuler comme mot, c’est que l’on peut annuler (supprimer les effets de) de plusieurs manières. Des manières qu’il vaut mieux éviter à moins de savoir exactement ce que l’on fait.
La méthode de ZZelle, annule les commits, mais pas par inversion. Par suppression. C’est ce qui rend la méthode dangereuse : en supprimant une partie de l’historique, on se maintenant s’assurer que tous les autres clones fassent la même modification (et pas d’autres) dans le même temps. Une situation très difficile à gérer.
Inverser a un autre sens de celui que ce que tu prends, Sam. Ça veut aussi dire prendre un truc, le mettre à la place d’un machin, et mettre le machin là où était le truc. C’est ce que j’avais compris de ton titre. Tu devrais changer ton titre, là c’est trop ambigu. Je préfère annuler: on a fait un truc, et on veut le défaire. Y’a plusieurs manières d’annuler, mais aussi plusieurs manières d’inverser !
ZZelle ne supprime rien et n’annule, elle chamboulle l’historique. en échangeant 2 commits. C’est dangereux, mais pas mal de giteux trouvent ça élégant, notamment pour les pull requests. Numpy demande à ce qu’un pull request soit compacté en un unique commit avant de merger, par exemple, pour garder l’hisorique global du projet plus clean.
Ok pour le changement de titre.
Par contre, je fais pas de tuteux pour les giteux. Je le fais pour ceux qui pigent pas git. Et j’en vois tous les jours qui suivent les tutos à base de reset –hard et de rebase et autres joyeuseté et qui bousillent leur environnement de travail à cause de ça. Ils perdent des jours là dessus. Parfois ils abandonnent Git juste à cause de ça. Franchement ça m’énerve quand on donne ces techniques dangereuses, c’est irresponsable. Au moins mettre un label énorme en rouge, et proposer AVANT l’alternative safe. Les gens se rendent pas compte…
Par ailleurs, le fait de compacter ses commits pour un pull request est une problématique différente. Ici on parle d’annuler un commit, et je pense que c’est beaucoup plus clair d’avoir une trace de l’annulation dans l’historique. De plus, compacter, c’est nécessaire sur un gros projet pour la lisibilité de l’histo. Mais franchement, pour une équipe qui a 3 dev, ce n’est pas du tout nécessaire. Qu’on recommande ça partout, juste pour “avoir un bel historique” ça me fout les nerfs. Est-ce que vous savez combien de personnes se sont fait niquez en faisant un rebase ? C’est une technique super advance, à recommander uniquement à ceux qui savent ce qu’ils font.
Merci d’avoir insisté pour changer le titre. C’est le genre de chose qu’on a tendance à ne pas voir (et même à ne pas vouloir voir) quand on a écrit l’article. Si il y avait juste une personne qui m’avait dit ça, je ne l’aurais pas fait. Mais comme vous avez tous, gentiment et patiemment, expliqué qu’il fallait le changer, ça a finit par rentrer.
Je ne le répète pas assez, mais vous êtes un super lectorat et je suis très content de vous avoir en comment.
un lien git merge vs rebase pour ceux qui passeraient par ici
Juste en passant, “…git reset –hard HEAD. Attention, cette dernière commande n’est pas réversible…” n’est pas tout à fait vrai.
La commande reflog de git permet de revenir à une position donnée du HEAD par lequel il est passé. Mais c’est un peu le coté obscur de git.
Sinon pour être sûr d’avoir bien compris. Si je faisais un revert pour chaque commit et qu’ensuite je squash l’ensemble des commits de revert en un seul commit, on obtient le même résultat ?
Cette command va aussi remettre à plat la copie de travail, et toute modif de celle-ci n’est pas dans l’histo git, donc on ne peut pas récupérer ça via git reflog.
Avec le squash, on obtient le même résultat, si tu fais el squash avant la publication des commits.
Merci pour le tutot ;)