paint-brush
Réécrire l'historique de Git en toute confiance : un guidepar@omerosenbaum
2,014 lectures
2,014 lectures

Réécrire l'historique de Git en toute confiance : un guide

par Omer Rosenbaum18m2023/04/27
Read on Terminal Reader

Trop long; Pour lire

Git est un système permettant d'enregistrer des instantanés d'un système de fichiers dans le temps. Un référentiel Git a trois "états" ou "arbres": l'index, la zone de staging et l'arbre de travail. Un répertoire de travail (ectrory) est n'importe quel répertoire de notre système de fichiers auquel est associé un référentiel Git.
featured image - Réécrire l'historique de Git en toute confiance : un guide
Omer Rosenbaum HackerNoon profile picture

En tant que développeur, vous travaillez tout le temps avec Git.


Êtes-vous déjà arrivé à un point où vous avez dit: "Uh-oh, qu'est-ce que je viens de faire?"

Lorsque les choses tournent mal avec Git, de nombreux ingénieurs se sentent impuissants (source : XKCD)


Cet article vous donnera les outils pour réécrire l'histoire en toute confiance.

Remarques avant de commencer

  1. J'ai également donné une conférence en direct couvrant le contenu de ce post. Si vous préférez une vidéo (ou souhaitez la regarder en même temps que la lecture) — vous pouvez la trouver .


  2. Je travaille sur un livre sur Git ! Êtes-vous intéressé à lire les versions initiales et à fournir des commentaires ? Envoyez-moi un email: [email protected]

Enregistrer les modifications dans Git

Avant de comprendre comment annuler des choses dans Git, vous devez d'abord comprendre comment nous enregistrons les modifications dans Git. Si vous connaissez déjà tous les termes, n'hésitez pas à sauter cette partie.


Il est très utile de considérer Git comme un système permettant d'enregistrer des instantanés d'un système de fichiers dans le temps. Considérant un référentiel Git, il a trois « états » ou « arborescences » :

Les trois "arbres" d'un dépôt Git (source : https://youtu.be/ozA1V00GIT8)


Habituellement, lorsque nous travaillons sur notre code source, nous travaillons à partir d'un répertoire de travail . Un répertoire de travail (ectrory) (ou arbre de travail ) est n'importe quel répertoire de notre système de fichiers auquel est associé un référentiel .


Il contient les dossiers et fichiers de notre projet ainsi qu'un répertoire appelé .git . J'ai décrit le contenu du dossier .git plus en détail dans un message précédent .


Après avoir apporté certaines modifications, vous souhaiterez peut-être les enregistrer dans votre référentiel . Un dépôt (en bref : repo ) est une collection de commits , dont chacun est une archive de ce à quoi ressemblait l'arborescence de travail du projet à une date passée, que ce soit sur votre machine ou sur celle de quelqu'un d'autre.


Un référentiel comprend également des éléments autres que nos fichiers de code, tels que HEAD , des branches, etc.


Entre les deux, nous avons l' index ou la staging area ; ces deux termes sont interchangeables. Lorsque nous checkout une branche, Git remplit l' index avec tout le contenu du fichier qui a été extrait pour la dernière fois dans notre répertoire de travail et à quoi il ressemblait lors de son extraction initiale.


Lorsque nous utilisons git commit , le commit est créé en fonction de l'état de l' index .


Ainsi, l' index, ou la zone de staging, est votre terrain de jeu pour le prochain commit. Vous pouvez travailler et faire ce que vous voulez avec l' index , y ajouter des fichiers, en supprimer des éléments, puis seulement lorsque vous êtes prêt, vous allez de l'avant et vous engagez dans le référentiel.


Il est temps de mettre la main à la pâte 🙌🏻


Utilisez git init pour initialiser un nouveau référentiel. Écrivez du texte dans un fichier appelé 1.txt :

Lancement d'un nouveau dépôt et création du premier fichier qu'il contient (source : https://youtu.be/ozA1V00GIT8)


Parmi les trois états d'arborescence décrits ci-dessus, où se trouve 1.txt maintenant ?


Dans l'arbre de travail, car il n'a pas encore été introduit dans l'index.

Le fichier "1.txt" fait désormais partie du répertoire de travail uniquement (source : https://youtu.be/ozA1V00GIT8)


Pour le mettre en scène , pour l'ajouter à l'index, utilisez git add 1.txt .

L'utilisation de `git add` organise le fichier afin qu'il soit désormais également dans l'index (source : https://youtu.be/ozA1V00GIT8)


Maintenant, nous pouvons utiliser git commit pour valider nos modifications dans le référentiel.

L'utilisation de `git commit` crée un objet de validation dans le référentiel (source : https://youtu.be/ozA1V00GIT8)


Vous avez créé un nouvel objet de validation, qui inclut un pointeur vers un arbre décrivant l'intégralité de l'arbre de travail. Dans ce cas, ce ne sera que 1.txt dans le dossier racine. En plus d'un pointeur vers l'arborescence, l'objet de validation inclut des métadonnées, telles que des horodatages et des informations sur l'auteur.


Pour plus d'informations sur les objets dans Git (tels que les commits et les arbres), regarde mon post précédent .


(Oui, "check out", jeu de mots voulu 😇)


Git nous indique également la valeur SHA-1 de cet objet commit. Dans mon cas, c'était c49f4ba (qui ne sont que les 7 premiers caractères de la valeur SHA-1, pour économiser de l'espace).


Si vous exécutez cette commande sur votre ordinateur, vous obtiendrez une valeur SHA-1 différente, car vous êtes un auteur différent ; également, vous créeriez le commit sur un horodatage différent.


Lorsque nous initialisons le référentiel, Git crée une nouvelle branche (nommée main par défaut). Et une branche dans Git est juste une référence nommée à un commit . Donc, par défaut, vous n'avez que la branche main . Que se passe-t-il si vous avez plusieurs succursales ? Comment Git sait-il quelle branche est la branche active ?


Git a un autre pointeur appelé HEAD , qui pointe (généralement) vers une branche, qui pointe ensuite vers un commit. D'ailleurs, sous la capuche, HEAD n'est qu'un fichier. Il comprend le nom de la branche avec quelques préfixes.


Il est temps d'introduire plus de changements dans le référentiel !


Maintenant, je veux en créer un autre. Créons donc un nouveau fichier et ajoutons-le à l'index, comme précédemment :

Le fichier "2.txt" se trouve dans le répertoire de travail et l'index après l'avoir stocké avec "git add" (source : https://youtu.be/ozA1V00GIT8)


Maintenant, il est temps d'utiliser git commit . Il est important de noter que git commit fait deux choses :


Tout d'abord, il crée un objet de validation, il existe donc un objet dans la base de données d'objets interne de Git avec une valeur SHA-1 correspondante. Ce nouvel objet commit pointe également vers le commit parent. C'est le commit vers lequel HEAD pointait lorsque vous avez écrit la commande git commit .

Un nouvel objet de commit a été créé, dans un premier temps - `main` pointe toujours vers le commit précédent (source : https://youtu.be/ozA1V00GIT8)


Deuxièmement, git commit déplace le pointeur de la branche active — dans notre cas, ce serait main , pour pointer vers l'objet commit nouvellement créé.

`git commit` met également à jour la branche active pour pointer vers l'objet commit nouvellement créé (source : https://youtu.be/ozA1V00GIT8)


Annulation des modifications

Pour réécrire l'historique, commençons par annuler le processus d'introduction d'un commit. Pour cela, nous allons faire connaissance avec la commande git reset , un outil super puissant.

git reset --soft

Donc, la toute dernière étape que vous avez faite auparavant était de git commit , ce qui signifie en fait deux choses : Git a créé un objet commit et a déplacé main , la branche active. Pour annuler cette étape, utilisez la commande git reset --soft HEAD~1 .


La syntaxe HEAD~1 fait référence au premier parent de HEAD . Si j'avais plus d'un commit dans le commit-graph, dites "Commit 3" pointant vers "Commit 2", qui à son tour pointe vers "Commit 1".


Et disons que HEAD pointait vers "Commit 3". Vous pouvez utiliser HEAD~1 pour faire référence à "Commit 2", et HEAD~2 ferait référence à "Commit 1".


Donc, revenons à la commande : git reset --soft HEAD~1


Cette commande demande à Git de modifier tout ce vers quoi HEAD pointe. (Remarque : dans les diagrammes ci-dessous, j'utilise *HEAD pour « tout ce vers quoi HEAD pointe »). Dans notre exemple, HEAD pointe vers main . Ainsi, Git ne changera que le pointeur de main pour pointer vers HEAD~1 . C'est-à-dire que main pointera vers "Commit 1".


Cependant, cette commande n'affectait pas l'état de l'index ou de l'arbre de travail. Donc, si vous utilisez git status , vous verrez que 2.txt est mis en scène, comme avant d'exécuter git commit .

Réinitialisation de `main` sur "Commit 1" (source : https://youtu.be/ozA1V00GIT8)


Qu'en est-il de git log? Il commencera par HEAD , ira à main , puis à "Commit 1". Notez que cela signifie que "Commit 2" n'est plus accessible depuis notre historique.


Cela signifie-t-il que l'objet commit de "Commit 2" est supprimé ? 🤔


Non, il n'est pas supprimé. Il réside toujours dans la base de données d'objets interne de Git.


Si vous poussez l'historique actuel maintenant, en utilisant git push , Git ne poussera pas "Commit 2" sur le serveur distant, mais l'objet commit existe toujours sur votre copie locale du référentiel.


Maintenant, validez à nouveau - et utilisez le message de validation de "Commit 2.1" pour différencier ce nouvel objet du "Commit 2" d'origine :

Création d'un nouveau commit (source : https://youtu.be/ozA1V00GIT8)


Pourquoi "Commit 2" et "Commit 2.1" sont-ils différents ? Même si nous avons utilisé le même message de validation, et même s'ils pointent vers le même objet arbre (du dossier racine composé de 1.txt et 2.txt ), ils ont toujours des horodatages différents, car ils ont été créés à des moments différents.


Dans le dessin ci-dessus, j'ai conservé "Commit 2" pour vous rappeler qu'il existe toujours dans la base de données d'objets interne de Git. "Commit 2" et "Commit 2.1" pointent maintenant vers "Commit 1", mais seul "Commit 2.1" est accessible depuis HEAD .

Git Reset --Mixte

Il est temps de revenir en arrière et de défaire plus loin. Cette fois, utilisez git reset --mixed HEAD~1 (remarque : --mixed est le commutateur par défaut pour git reset ).


Cette commande démarre de la même manière que git reset --soft HEAD~1 . Cela signifie qu'il prend le pointeur de tout ce que HEAD pointe maintenant, qui est la branche main , et le définit sur HEAD~1 , dans notre exemple - "Commit 1".

La première étape de `git reset --mixed` est identique à `git reset --soft` (source : https://youtu.be/ozA1V00GIT8)


Ensuite, Git va plus loin, annulant efficacement les modifications que nous avons apportées à l'index. C'est-à-dire, changer l'index pour qu'il corresponde au HEAD actuel, le nouveau HEAD après l'avoir défini à la première étape.


Si nous lancions git reset --mixed HEAD~1 , cela signifie que HEAD serait défini sur HEAD~1 ("Commit 1"), puis Git ferait correspondre l'index à l'état de "Commit 1" - dans ce cas, il signifie que 2.txt ne fera plus partie de l'index.

La deuxième étape de `git reset --mixed` consiste à faire correspondre l'index avec le nouveau `HEAD` (source : https://youtu.be/ozA1V00GIT8)


Il est temps de créer un nouveau commit avec l'état du "Commit 2" d'origine. Cette fois, nous devons à nouveau mettre en scène 2.txt avant de le créer :

Création de « Commit 2.2 » (source : https://youtu.be/ozA1V00GIT8)


Réinitialisation de Git --Hard

Allez-y, annulez encore plus !


Allez-y et lancez git reset --hard HEAD~1


Encore une fois, Git commence par l'étape --soft , en définissant tout ce que HEAD pointe vers ( main ), sur HEAD~1 ("Commit 1").

La première étape de `git reset --hard` est la même que `git reset --soft` (source : https://youtu.be/ozA1V00GIT8)


Jusqu'ici, tout va bien.


Ensuite, passez à l'étape --mixed , en faisant correspondre l'index avec HEAD . Autrement dit, Git annule la mise en scène de 2.txt .

La deuxième étape de `git reset --hard` est identique à `git reset --mixed` (source : https://youtu.be/ozA1V00GIT8)


Il est temps pour l'étape --hard où Git va encore plus loin et fait correspondre le répertoire de travail avec l'étape de l'index. Dans ce cas, cela signifie supprimer également 2.txt du répertoire de travail.

La troisième étape de `git reset --hard` fait correspondre l'état du répertoire de travail avec celui de l'index (source : https://youtu.be/ozA1V00GIT8)


(**Remarque : dans ce cas précis, le fichier n'est pas suivi, il ne sera donc pas supprimé du système de fichiers ; ce n'est cependant pas vraiment important pour comprendre git reset ).


Donc, pour introduire un changement dans Git, vous avez trois étapes. Vous modifiez le répertoire de travail, l'index ou la zone de staging, puis vous validez un nouvel instantané avec ces modifications. Pour annuler ces modifications :


  • Si nous utilisons git reset --soft , nous annulons l'étape de validation.


  • Si nous utilisons git reset --mixed , nous annulons également l'étape de mise en scène.


  • Si nous utilisons git reset --hard , nous annulons les modifications apportées au répertoire de travail.

Scénarios réels !

Scénario 1

Donc, dans un scénario réel, écrivez "J'aime Git" dans un fichier ( love.txt ), car nous aimons tous Git 😍. Allez-y, mettez en scène et validez ceci également :

Création de "Commit 2.3" (source : https://youtu.be/ozA1V00GIT8)


Oh, oups !


En fait, je ne voulais pas que tu le commettes.


Ce que je voulais vraiment que vous fassiez, c'est d'écrire quelques mots d'amour supplémentaires dans ce fichier avant de le valider.

Que pouvez-vous faire?


Eh bien, une façon de surmonter cela serait d'utiliser git reset --mixed HEAD~1 , en annulant efficacement les actions de validation et de mise en scène que vous avez prises :

Annulation des étapes de mise en scène et de validation (source : https://youtu.be/ozA1V00GIT8)


Donc, les points main sont à nouveau "Commit 1", et love.txt ne fait plus partie de l'index. Cependant, le fichier reste dans le répertoire de travail. Vous pouvez maintenant continuer et ajouter plus de contenu :

Ajouter plus de paroles d'amour (source : https://youtu.be/ozA1V00GIT8)


Allez-y, mettez en scène et commitez votre fichier :

Création du nouveau commit avec l'état souhaité (source : https://youtu.be/ozA1V00GIT8)


Bravo 👏🏻


Vous avez cet historique clair et agréable de "Commit 2.4" pointant vers "Commit 1".


Nous avons maintenant un nouvel outil dans notre boîte à outils, git reset 💪🏻

git reset est maintenant dans notre boîte à outils (source : https://youtu.be/ozA1V00GIT8)


Cet outil est super, super utile, et vous pouvez accomplir presque n'importe quoi avec lui. Ce n'est pas toujours l'outil le plus pratique à utiliser, mais il est capable de résoudre presque tous les scénarios d'historique de réécriture si vous l'utilisez avec précaution.


Pour les débutants, je recommande d'utiliser uniquement git reset presque chaque fois que vous souhaitez annuler dans Git. Une fois que vous vous sentez à l'aise avec, il est temps de passer à d'autres outils.

Scénario #2

Considérons un autre cas.


Créez un nouveau fichier nommé new.txt ; mettre en scène et valider :

Création de `new.txt` et de "Commit 3" (source : https://youtu.be/ozA1V00GIT8)


Oops. En fait, c'est une erreur. Vous étiez sur main , et je voulais que vous créiez ce commit sur une branche de fonctionnalité. Mon mauvais 😇


Il y a deux outils les plus importants que je veux que vous retiriez de cet article. Le second est git reset . La première et de loin la plus importante consiste à dresser un tableau blanc de l'état actuel par rapport à l'état dans lequel vous voulez être.


Pour ce scénario, l'état actuel et l'état souhaité ressemblent à ceci :

Scénario n° 2 : états actuels et états souhaités (source : https://youtu.be/ozA1V00GIT8)


Vous remarquerez trois changements :


  1. points main à "Commit 3" (le bleu) dans l'état actuel, mais à "Commit 2.4" dans l'état souhaité.


  2. La branche feature n'existe pas dans l'état actuel, mais elle existe et pointe vers "Commit 3" dans l'état souhaité.


  3. HEAD pointe sur main dans l'état actuel et sur feature dans l'état souhaité.


Si vous pouvez dessiner ceci et que vous savez comment utiliser git reset , vous pouvez certainement vous sortir de cette situation.


Encore une fois, la chose la plus importante est de respirer et de tirer cela.


En observant le dessin ci-dessus, comment passe-t-on de l'état actuel à celui souhaité ?


Il existe bien sûr plusieurs façons, mais je ne présenterai qu'une seule option pour chaque scénario. N'hésitez pas à jouer avec d'autres options également.


Vous pouvez commencer par utiliser git reset --soft HEAD~1 . Cela définirait main pour pointer vers le commit précédent, "Commit 2.4":

Changer 'principal' ; "Le commit 3 est flou car il est toujours là, mais pas accessible (source : https://youtu.be/ozA1V00GIT8)


En jetant un coup d'œil au diagramme actuel vs souhaité, vous pouvez voir que vous avez besoin d'une nouvelle branche, n'est-ce pas ? Vous pouvez utiliser git switch -c feature pour cela ou git checkout -b feature (qui fait la même chose):

Création de la branche "fonctionnalité" (source : https://youtu.be/ozA1V00GIT8)


Cette commande met également à jour HEAD pour pointer vers la nouvelle branche.


Depuis que vous avez utilisé git reset --soft , vous n'avez pas modifié l'index, il a donc exactement l'état que vous souhaitez valider - comme c'est pratique ! Vous pouvez simplement vous engager sur la branche feature :

S'engager dans la branche "fonctionnalité" (source : https://youtu.be/ozA1V00GIT8)


Et vous êtes arrivé à l'état souhaité 🎉

Scénario #3

Prêt à appliquer vos connaissances à d'autres cas ?


Ajoutez quelques modifications à love.txt et créez également un nouveau fichier appelé cool.txt . Mettez-les en scène et engagez :

Création de "Commit 4" (source : https://youtu.be/ozA1V00GIT8)


Oh, oups, en fait je voulais que vous créiez deux commits séparés, un avec chaque changement 🤦🏻

Vous voulez essayer celui-ci vous-même ?


Vous pouvez annuler les étapes de validation et de préproduction :

Annuler la validation et la mise en scène à l'aide de `git reset --mixed HEAD~1` (source : https://youtu.be/ozA1V00GIT8)


Suite à cette commande, l'index n'inclut plus ces deux modifications, mais elles sont toujours dans votre système de fichiers. Alors maintenant, si vous ne faites que mettre en scène love.txt , vous pouvez le valider séparément, puis faire de même pour cool.txt :

S'engager séparément (source : https://youtu.be/ozA1V00GIT8)


sympa 😎

Scénario #4

Créez un nouveau fichier ( new_file.txt ) avec du texte et ajoutez du texte à love.txt . Mettez en scène les deux modifications et validez-les :

Un nouveau commit (source : https://youtu.be/ozA1V00GIT8)


Oups 🙈🙈


Donc cette fois, je voulais que ce soit sur une autre branche, mais pas une nouvelle branche, plutôt une branche déjà existante.


Alors que peux-tu faire?


Je vais vous donner un indice. La réponse est vraiment courte et vraiment facile. Que fait-on en premier ?


Non, pas reset . Nous dessinons. C'est la première chose à faire, car cela rendrait tout le reste beaucoup plus facile. Voici donc l'état actuel :

Le nouveau commit sur "main" apparaît en bleu (source : https://youtu.be/ozA1V00GIT8)


Et l'état souhaité ?

Nous voulons que le commit "bleu" soit sur une autre branche "existante" (source : https://youtu.be/ozA1V00GIT8)


Comment passer de l'état actuel à l'état souhaité, qu'est-ce qui serait le plus simple ?


Donc, une façon serait d'utiliser git reset comme vous l'avez fait auparavant, mais il y a une autre façon que j'aimerais que vous essayiez.


Tout d'abord, déplacez HEAD pour pointer vers la branche existing :

Passez à la branche "existante" (source : https://youtu.be/ozA1V00GIT8)


Intuitivement, ce que vous voulez faire est de prendre les modifications introduites dans le commit bleu et d'appliquer ces modifications ("copier-coller") au-dessus de la branche existing . Et Git a un outil juste pour ça.


Pour demander à Git de prendre les changements introduits entre ce commit et son commit parent et d'appliquer simplement ces changements sur la branche active, vous pouvez utiliser git cherry-pick . Cette commande prend les changements introduits dans la révision spécifiée et les applique au commit actif.


Il crée également un nouvel objet commit et met à jour la branche active pour pointer vers ce nouvel objet.

Utilisation de `git cherry-pick` (source : https://youtu.be/ozA1V00GIT8)


Dans l'exemple ci-dessus, j'ai spécifié l'identifiant SHA-1 du commit créé, mais vous pouvez également utiliser git cherry-pick main , car le commit dont nous appliquons les modifications est celui vers lequel pointe main .


Mais nous ne voulons pas que ces changements existent sur la branche main . git cherry-pick n'a appliqué les modifications qu'à la branche existing . Comment pouvez-vous les supprimer de main ?


Une façon serait de switch à main , puis d'utiliser git reset --hard HEAD~1 :

Réinitialisation de `main` (source : https://youtu.be/ozA1V00GIT8)


Tu l'as fait! 💪🏻


Notez que git cherry-pick calcule en fait la différence entre le commit spécifié et son parent, puis les applique au commit actif. Cela signifie que parfois, Git ne pourra pas appliquer ces modifications car vous pourriez avoir un conflit, mais c'est un sujet pour un autre article.


Notez également que vous pouvez demander à Git de cherry-pick les modifications introduites dans n'importe quel commit, pas seulement les commits référencés par une branche.


Nous avons acquis un nouvel outil, nous avons donc git reset ainsi que git cherry-pick à notre actif.

Scénario #5

Bon, alors un autre jour, un autre repo, un autre problème.


Créez un commit :

Un autre commit (source : https://youtu.be/ozA1V00GIT8)


Et push -le sur le serveur distant :

(source : https://youtu.be/ozA1V00GIT8)


Euh, oups 😓…


Je viens de remarquer quelque chose. Il y a une faute de frappe. J'ai écrit This is more tezt au lieu de This is more text . Oups. Alors quel est le gros problème maintenant ? J'ai push ed, ce qui signifie que quelqu'un d'autre a peut-être déjà pull ces modifications.


Si je remplace ces modifications en utilisant git reset , comme nous l'avons fait jusqu'à présent, nous aurons des historiques différents, et l'enfer pourrait se déchaîner. Vous pouvez réécrire votre propre copie du référentiel autant que vous le souhaitez jusqu'à ce que vous le push .


Une fois que vous push le changement, vous devez être très certain que personne d'autre n'a récupéré ces changements si vous allez réécrire l'histoire.


Alternativement, vous pouvez utiliser un autre outil appelé git revert . Cette commande prend le commit que vous lui fournissez et calcule le Diff à partir de son commit parent, tout comme git cherry-pick , mais cette fois, il calcule les changements inverses.


Donc, si dans le commit spécifié vous avez ajouté une ligne, l'inverse supprimerait la ligne, et vice versa.

Utiliser `git revert` pour annuler les modifications (source : https://youtu.be/ozA1V00GIT8)


git revert a créé un nouvel objet commit, ce qui signifie qu'il s'agit d'un ajout à l'historique. En utilisant git revert , vous n'avez pas réécrit l'historique. Vous avez admis votre erreur passée, et ce commit est une reconnaissance que vous avez fait une erreur et maintenant vous l'avez corrigée.


Certains diraient que c'est la voie la plus mature. Certains diront que ce n'est pas un historique aussi propre que si vous utilisiez git reset pour réécrire le commit précédent. Mais c'est une façon d'éviter de réécrire l'histoire.


Vous pouvez maintenant corriger la faute de frappe et valider à nouveau :

Rétablir les modifications (source : https://youtu.be/ozA1V00GIT8)


Votre boîte à outils est maintenant chargée avec un nouvel outil brillant, revert :

Notre boîte à outils (source : https://youtu.be/ozA1V00GIT8)


Scénario #6

Travaillez, écrivez du code et ajoutez-le à love.txt . Mettez en scène ce changement et validez-le :

Un autre commit (source : https://youtu.be/ozA1V00GIT8)


J'ai fait la même chose sur ma machine et j'ai utilisé la touche fléchée vers le Up de mon clavier pour revenir aux commandes précédentes, puis j'ai appuyé sur Enter et… Wow.


Oups.

Est-ce que je viens de "git reset -- hard" ? (source : https://youtu.be/ozA1V00GIT8)


Ai-je simplement utilisé git reset --hard ? 😨


Que s'est-il réellement passé ? Git a déplacé le pointeur vers HEAD~1 , donc le dernier commit, avec tout mon précieux travail, n'est pas accessible depuis l'historique actuel. Git a également désorganisé toutes les modifications de la zone de staging, puis a fait correspondre le répertoire de travail à l'état de la zone de staging.


C'est-à-dire que tout correspond à cet état où mon travail est… parti.


Temps de panique. Paniquer.

Mais, vraiment, y a-t-il une raison de paniquer ? Pas vraiment… Nous sommes des gens détendus. Qu'est-ce qu'on fait? Eh bien, intuitivement, le commit a-t-il vraiment, vraiment disparu ? Non pourquoi pas? Il existe toujours dans la base de données interne de Git.


Si je savais seulement où c'est, je connaîtrais la valeur SHA-1 qui identifie ce commit, nous pourrions le restaurer. Je pourrais même annuler l'annulation et reset à ce commit.


Donc, la seule chose dont j'ai vraiment besoin ici est le SHA-1 du commit "supprimé".


Donc la question est, comment puis-je le trouver? git log serait-il utile ?


Eh bien pas vraiment. git log irait à HEAD , qui pointe vers main , qui pointe vers le commit parent du commit que nous recherchons. Ensuite, git log remonterait à travers la chaîne parente, qui n'inclut pas le commit avec mon précieux travail.

`git log` n'aide pas dans ce cas (source : https://youtu.be/ozA1V00GIT8)


Heureusement, les personnes très intelligentes qui ont créé Git ont également créé un plan de sauvegarde pour nous, et cela s'appelle le reflog .


Pendant que vous travaillez avec Git, chaque fois que vous modifiez HEAD , ce que vous pouvez faire en utilisant git reset , mais aussi d'autres commandes comme git switch ou git checkout , Git ajoute une entrée au reflog .


`git reflog` nous montre où était `HEAD` (source : https://youtu.be/ozA1V00GIT8)


Nous avons trouvé notre engagement ! C'est celui qui commence par 0fb929e .


Nous pouvons également l'identifier par son "surnom" - HEAD@{1} . Ainsi, comme Git utilise HEAD~1 pour accéder au premier parent de HEAD , et HEAD~2 pour faire référence au deuxième parent de HEAD et ainsi de suite, Git utilise HEAD@{1} pour faire référence au premier parent reflog de HEAD , où HEAD pointait à l'étape précédente.


On peut aussi demander à git rev-parse de nous montrer sa valeur :

(source : https://youtu.be/ozA1V00GIT8)


Une autre façon de voir le reflog est d'utiliser git log -g , qui demande à git log de considérer réellement le reflog :

La sortie de `git log -g` (source : https://youtu.be/ozA1V00GIT8)


Nous voyons ci-dessus que le reflog , tout comme HEAD , pointe vers main , qui pointe vers « Commit 2 ». Mais le parent de cette entrée dans le reflog pointe vers "Commit 3".


Donc, pour revenir à "Commit 3", vous pouvez simplement utiliser git reset --hard HEAD@{1} (ou la valeur SHA-1 de "Commit 3") :

(source : https://youtu.be/ozA1V00GIT8)


Et maintenant, si nous git log :

Notre histoire est de retour !!! (source : https://youtu.be/ozA1V00GIT8)


Nous avons sauvé la journée ! 🎉👏🏻


Que se passerait-il si j'utilisais à nouveau cette commande ? Et exécuté git commit --reset HEAD@{1} ? Git définirait HEAD sur l'endroit où HEAD pointait avant la dernière reset , c'est-à-dire sur "Commit 2". On peut continuer toute la journée :

(source : https://youtu.be/ozA1V00GIT8)


En regardant maintenant notre boîte à outils, elle est remplie d'outils qui peuvent vous aider à résoudre de nombreux cas où les choses tournent mal dans Git :

Notre boîte à outils est assez complète ! (source : https://youtu.be/ozA1V00GIT8)


Avec ces outils, vous comprenez mieux maintenant le fonctionnement de Git. Il existe d'autres outils qui vous permettraient de réécrire spécifiquement l'historique, git rebase ), mais vous avez déjà beaucoup appris dans cet article. Dans les prochains articles, je plongerai également dans git rebase .


L'outil le plus important, encore plus important que les cinq outils répertoriés dans cette boîte à outils, est de dresser un tableau blanc de la situation actuelle par rapport à celle souhaitée. Faites-moi confiance, cela rendra chaque situation moins intimidante et la solution plus claire.

En savoir plus sur Git

J'ai également donné une conférence en direct couvrant le contenu de ce message. Si vous préférez une vidéo (ou souhaitez la regarder en même temps que la lecture) — vous pouvez la trouver .


En général, ma chaîne YouTube couvre de nombreux aspects de Git et de ses composants internes ; vous êtes les bienvenus Vérifiez-le (jeu de mots 😇)

A propos de l'auteur

Omer Rosenbaum est le CTO et co-fondateur de nager , un outil de développement qui aide les développeurs et leurs équipes à gérer les connaissances sur leur base de code avec une documentation interne à jour. Omer est le fondateur de Check Point Security Academy et était le responsable de la cybersécurité à l'ITC, une organisation éducative qui forme des professionnels talentueux pour développer des carrières dans la technologie.


Omer est titulaire d'une maîtrise en linguistique de l'Université de Tel-Aviv et est le créateur du Brève chaîne YouTube .


Publié pour la première fois ici