Aller au contenu
English

Récupérer son travail

  • Leçon 12
  • 40 min
  • Module 4 : Résoudre et récupérer

Ce que vous saurez faire

Récupérez votre travail après avoir déplacé une branche vers le mauvais commit ou supprimé une branche. Après cette leçon, vous pourrez retrouver un commit perdu, le récupérer et conserver aussi le travail de votre collègue.

Avant de commencerCe qu’il faut savoir et avoir sous la mainEnviron 40 minutes

Ce que vous devez savoir

Ce qu’il vous faut sur votre ordinateur

Quand vous réinitialisez une branche, Git ne supprime pas immédiatement les commits qui la quittent. Il les conserve pendant un certain temps. Le problème est de trouver leurs empreintes, car aucune branche ni aucun git log ne les montre maintenant.

Le reflog (journal des références) enregistre les positions de HEAD et de chaque branche, de la plus récente à la plus ancienne. Git ajoute une entrée chaque fois que HEAD se déplace : commit, changement de branche, réinitialisation, fusion, récupération ou clonage. git reflog montre les entrées de HEAD :

git reflog (example)
2bf80ca HEAD@{0}: reset: moving to HEAD~2
41faf11 HEAD@{1}: commit: Add ticket 107
121ec42 HEAD@{2}: commit: Add ticket 106
2bf80ca HEAD@{3}: clone: from /Users/amira/projects/c14-lab/team-remote.git

Lisez chaque ligne de gauche à droite :

Partie Exemple Signification
Empreinte 41faf11 Le commit où se trouvait HEAD après cette action.
Nom HEAD@{1} « La position de HEAD il y a un déplacement ». HEAD@{0} est sa position actuelle.
Action commit: Add ticket 107 Ce qui a déplacé HEAD, et le message du commit ou la cible.

Trois limites sont importantes :

  • Le reflog est local. Il existe seulement dans votre clone. Git ne le pousse pas, et un nouveau clone commence avec un reflog vide. Votre collègue ne peut pas utiliser votre reflog.
  • Les entrées expirent. Par défaut, Git conserve une entrée pendant 90 jours. Pour un commit qu’aucune branche ne contient, il conserve l’entrée pendant 30 jours. Ensuite, Git peut supprimer le commit.
  • Il enregistre seulement les commits. Si vous n’avez jamais enregistré un changement dans un commit ou un stash, le reflog ne peut rien récupérer. git restore et git reset --hard suppriment définitivement ces changements.

Exemple détaillé : annuler une réinitialisation accidentelle

Section intitulée « Exemple détaillé : annuler une réinitialisation accidentelle »

Amira crée un clone jetable, puis y crée deux commits locaux :

Terminal (Amira)
cd ~/projects/c14-lab
git clone team-remote.git scratch
cd scratch

Elle ajoute une ligne pour le billet 106 à tickets.txt et l’enregistre avec git commit -am "Add ticket 106". Elle fait ensuite la même chose pour le billet 107 :

git log --oneline -3
41faf11 (HEAD -> main) Add ticket 107
121ec42 Add ticket 106
2bf80ca (origin/main, origin/HEAD) Revert "Update the accounts team extension"

Elle veut retirer seulement le dernier commit, mais elle tape HEAD~2 au lieu de HEAD~1 :

Terminal (Amira, in scratch)
git reset --hard HEAD~2
Sortie
HEAD is now at 2bf80ca Revert "Update the accounts team extension"

Les deux commits ont disparu de main et de git log. Elle consulte le reflog. -5 montre les cinq entrées les plus récentes :

Terminal (Amira, in scratch)
git reflog -5
Sortie
2bf80ca HEAD@{0}: reset: moving to HEAD~2
41faf11 HEAD@{1}: commit: Add ticket 107
121ec42 HEAD@{2}: commit: Add ticket 106
2bf80ca HEAD@{3}: clone: from /Users/amira/projects/c14-lab/team-remote.git

L’entrée avant la réinitialisation est 41faf11, son commit pour le billet 107. Son parent est le commit pour le billet 106. Donc, 41faf11 comprend ses deux commits.

Elle pourrait exécuter git reset --hard 41faf11 pour remettre main à sa position précédente. Mais si elle copie la mauvaise empreinte, elle fait une deuxième erreur. Il est plus sûr de placer d’abord une nouvelle branche sur le commit. Une branche ne change aucun fichier, et Amira peut l’inspecter :

Terminal (Amira, in scratch)
git branch rescue 41faf11
git log --oneline -3 rescue
Sortie
41faf11 (rescue) Add ticket 107
121ec42 Add ticket 106
2bf80ca (HEAD -> main, origin/main, origin/HEAD) Revert "Update the accounts team extension"

Ce sont les bons commits. main est derrière rescue, donc elle avance main avec une fusion en avance rapide. --ff-only signifie « seulement si c’est une avance rapide ». Sinon, Git s’arrête et ne change rien. Elle supprime ensuite la branche de récupération :

Terminal (Amira, in scratch)
git merge --ff-only rescue
git branch -d rescue
Sortie
Updating 2bf80ca..41faf11
Fast-forward
tickets.txt | 2 ++
1 file changed, 2 insertions(+)
Deleted branch rescue (was 41faf11).

main pointe de nouveau vers 41faf11, et les deux commits sont de retour.

Exemple détaillé : récupérer une branche supprimée

Section intitulée « Exemple détaillé : récupérer une branche supprimée »

Amira essaie une idée sur une branche, try-priorities, avec un commit. Elle revient à main et supprime la branche. git branch -d refuse, car la branche contient un commit qui n’est sur aucune autre branche :

Sortie
error: the branch 'try-priorities' is not fully merged
hint: If you are sure you want to delete it, run 'git branch -D try-priorities'

Cet avertissement protège le travail. Elle utilise -D, qui supprime quand même la branche :

Terminal (Amira, in scratch)
git branch -D try-priorities
Sortie
Deleted branch try-priorities (was edc37a8).

Elle réalise ensuite qu’elle a finalement besoin de cette idée. La sortie ci-dessus montre l’empreinte : (was edc37a8) (était à edc37a8). Si cette sortie n’est plus à l’écran, utilisez le reflog. L’empreinte se trouve dans l’entrée de son commit sur la branche :

git reflog -3
41faf11 HEAD@{0}: checkout: moving from try-priorities to main
edc37a8 HEAD@{1}: commit: Add priority levels
41faf11 HEAD@{2}: checkout: moving from main to try-priorities

Une branche est seulement une étiquette sur un commit, alors elle recrée l’étiquette :

Terminal (Amira, in scratch)
git branch try-priorities edc37a8
git log --oneline -2 try-priorities
Sortie
edc37a8 (try-priorities) Add priority levels
41faf11 (HEAD -> main) Add ticket 107

Stash : mettre des changements de côté temporairement

Section intitulée « Stash : mettre des changements de côté temporairement »

Un stash (réserve temporaire) conserve des changements non enregistrés dans un commit pour vous permettre de travailler sur autre chose. git stash push sauvegarde les changements et remet vos fichiers dans l’état du dernier commit. git stash pop remet les changements en place.

Amira a fait la moitié du travail sur le billet 108 dans tickets.txt. Elle a aussi un nouveau fichier, draft.txt, que Git ne suit pas. Un collègue lui demande de vérifier quelque chose sur un main propre :

Terminal (Amira, in scratch)
git status --short
git stash push -m "Half-done ticket 108"
git status --short
git stash list
Sortie
M tickets.txt
?? draft.txt
Saved working directory and index state On main: Half-done ticket 108
?? draft.txt
stash@{0}: On main: Half-done ticket 108

-m donne un message au stash pour qu’elle puisse le reconnaître dans git stash list. Le changement de tickets.txt est dans le stash. Mais draft.txt reste dans le dossier : par défaut, le stash ne prend pas les fichiers non suivis. git stash push -u les prend aussi.

Quand elle est prête, elle récupère le changement :

Terminal (Amira, in scratch)
git stash pop
Output (last line)
Dropped refs/stash@{0} (e42f7c918e3b194e6e2218fcf006ec4e10a15bed)

Le changement est de retour dans tickets.txt, et le stash est retiré de la liste.

  • Les fichiers non suivis restent sur place, sauf si vous utilisez -u.
  • Un stash est local. git push ne l’envoie pas. Si votre ordinateur tombe en panne, le stash est perdu.
  • pop peut causer un conflit. Si les mêmes lignes ont changé après leur mise en stash, pop s’arrête avec un conflit. Le stash reste alors dans la liste. Consultez l’erreur courante ci-dessous.
  • Il est facile d’oublier les stashs. Après une semaine, stash@{0}: On main: WIP (sur main : travail en cours) vous dit très peu de choses.

Pour du travail qui doit attendre plus de quelques minutes, un commit sur sa propre branche est plus sûr. Le reflog l’enregistre, vous pouvez le pousser, et son message décrit son contenu :

Fenêtre de terminal
git switch -c wip-ticket-108
git commit -am "WIP: ticket 108, cause not found yet"
git switch main

Exercice guidé : perdre des commits et les récupérer

Section intitulée « Exercice guidé : perdre des commits et les récupérer »
  1. Si vous avez fait les exemples détaillés dans votre propre clone scratch, supprimez ce clone. Sinon, git clone s’arrête à l’étape suivante, car le dossier existe. rm -rf supprime un dossier sans possibilité d’annuler. Vérifiez donc le nom avant d’appuyer sur Enter (Entrée) :

    Fenêtre de terminal
    cd ~/projects/c14-lab
    rm -rf scratch

    ls ne montre plus scratch.

  2. Créez un nouveau clone jetable :

    Fenêtre de terminal
    cd ~/projects/c14-lab
    git clone team-remote.git scratch
    cd scratch
  3. Ajoutez une ligne pour le billet 106 à tickets.txt, sauvegardez et créez un commit :

    Fenêtre de terminal
    git commit -am "Add ticket 106"
  4. Ajoutez une ligne pour le billet 107, sauvegardez et créez un commit :

    Fenêtre de terminal
    git commit -am "Add ticket 107"
  5. Exécutez pwd. Puis retirez les deux commits par erreur :

    Fenêtre de terminal
    git reset --hard HEAD~2

    git log --oneline -1 montre le commit du dépôt distant. Vos deux commits ne sont pas dans le journal.

  6. Retrouvez-les :

    Fenêtre de terminal
    git reflog -5

    L’entrée HEAD@{1} montre commit: Add ticket 107 (commit : ajouter le billet 107). Notez son empreinte.

  7. Placez une branche sur cette empreinte et vérifiez-la. Utilisez votre empreinte au lieu de 41faf11 :

    Fenêtre de terminal
    git branch rescue 41faf11
    git log --oneline -3 rescue

    La sortie montre vos deux commits.

  8. Avancez main et supprimez la branche de récupération :

    Fenêtre de terminal
    git merge --ff-only rescue
    git branch -d rescue

    La sortie comprend Fast-forward (avance rapide). git log --oneline -2 montre de nouveau les deux commits.

  9. Créez une branche avec un commit, puis revenez à main :

    Fenêtre de terminal
    git switch -c try-priorities
    echo "Priority levels: low, normal, urgent." > priorities.txt
    git add priorities.txt
    git commit -m "Add priority levels"
    git switch main
  10. Exécutez pwd. Puis supprimez la branche :

    Fenêtre de terminal
    git branch -D try-priorities

    La sortie est Deleted branch try-priorities (was …) (branche try-priorities supprimée, était à …), avec une empreinte.

  11. Recréez la branche avec cette empreinte. Utilisez votre empreinte au lieu de edc37a8. Puis vérifiez-la :

    Fenêtre de terminal
    git branch try-priorities edc37a8
    git log --oneline -1 try-priorities

    La sortie montre Add priority levels (ajouter des niveaux de priorité).

  12. Ajoutez une ligne pour le billet 108 à tickets.txt, mais ne créez pas de commit. Mettez-la en stash et vérifiez :

    Fenêtre de terminal
    git stash push -m "Half-done ticket 108"
    git stash list
    git status --short

    git stash list montre votre message. git status --short n’affiche rien.

  13. Récupérez le changement avec git stash pop. git status --short montre M tickets.txt (tickets.txt modifié).

À vous : récupérez votre commit et gardez celui de Sam

Section intitulée « À vous : récupérez votre commit et gardez celui de Sam »

Voici la vérification de fin du module 4. Vous récupérez votre commit perdu, et le travail de votre collègue doit rester.

  1. Supprimez l’ancien scratch, puis créez-en un nouveau. Dans ce dépôt, faites un commit (enregistrement des modifications) d’une ligne pour le billet 106.
  2. Dans sam-notes, exécutez git pull. Ajoutez Network printer: ext. 4520 (imprimante réseau : poste 4520) à contacts.txt, faites un commit avec le message Add the network printer contact (ajouter le contact de l’imprimante réseau), puis poussez.
  3. Dans scratch, exécutez git fetch. Faites ensuite cette erreur volontairement : git reset --hard origin/main. Vous vouliez « mettre à jour depuis le dépôt distant », et votre commit du billet 106 a quitté main.
  4. Récupérez votre commit pour que main, dans scratch, contienne à la fois votre commit du billet 106 et le contact d’imprimante de Sam. Ne poussez rien depuis scratch.

Montrez ensuite le résultat avec git log --oneline --graph -4, tail -1 contacts.txt, tail -1 tickets.txt et sh check.sh.

Afficher une réponse modèle

Après l’erreur, le reflog (journal des déplacements des références) montre le commit perdu à HEAD@{1} :

git reflog -4
6a64a93 HEAD@{0}: reset: moving to origin/main
a89b80c HEAD@{1}: commit: Add ticket 106
2bf80ca HEAD@{2}: clone: from /Users/amira/projects/c14-lab/team-remote.git

Créez une branche sur ce commit. Fusionnez-la ensuite dans main. Ce n’est pas une avance rapide, car main contient maintenant le commit de Sam et rescue contient le vôtre. Git crée donc un commit de fusion qui conserve les deux :

Fenêtre de terminal
git branch rescue a89b80c
git merge --no-edit rescue
git log --oneline --graph -4
tail -1 contacts.txt
tail -1 tickets.txt
sh check.sh
Sortie (extrait)
Merge made by the 'ort' strategy.
tickets.txt | 1 +
1 file changed, 1 insertion(+)
* 50d5765 (HEAD -> main) Merge branch 'rescue'
|\
| * a89b80c (rescue) Add ticket 106
* | 6a64a93 (origin/main, origin/HEAD) Add the network printer contact
|/
* 2bf80ca Revert "Update the accounts team extension"
Network printer: ext. 4520
Ticket 106: keyboard missing keys. Ordered a new keyboard.
PASS: 6 ticket lines checked

Le graphique montre les deux lignes de travail : votre commit récupéré et celui de Sam. Vous pouvez maintenant supprimer l’étiquette avec git branch -d rescue. Le commit de Sam n’a jamais été en danger dans le dépôt distant. Le danger était à l’étape suivante : si vous aviez ramené main à a89b80c avec reset, votre main ne contiendrait pas le commit de Sam.

Autre réponse correcte : git reset --hard a89b80c, puis git pull --no-rebase --no-edit. Les fichiers sont identiques. Seul le message du commit de fusion diffère.

Erreur courante

git stash pop s'arrête sur un conflit

Symptôme : vous avez mis de côté une modification de tickets.txt, puis fait un commit modifiant les mêmes lignes. git stash pop affiche :

Output (first and last lines)
Auto-merging tickets.txt
CONFLICT (content): Merge conflict in tickets.txt
...
The stash entry is kept in case you need it again.

git stash list montre encore le stash (modifications mises de côté). tickets.txt contient des marqueurs de conflit. Ici, les noms des côtés diffèrent de ceux d’une fusion :

tickets.txt (end)
<<<<<<< Updated upstream
Ticket 107: cannot print in colour. Installed the colour driver. Fixed.
||||||| Stash base
Ticket 107: cannot print in colour. Installed the colour driver.
=======
Ticket 107: cannot print in colour. Installed the colour driver.
Ticket 108: VPN drops every hour.
>>>>>>> Stashed changes

Cause : Updated upstream (version actuelle) correspond à votre commit actuel. Stashed changes (modifications mises de côté) correspond au stash. Les deux ont modifié des lignes voisines depuis la mise de côté. Git ne peut donc pas appliquer le stash seul. Il conserve le stash pour ne pas perdre la modification.

Solution :

  1. Résolvez le conflit comme dans Les conflits, étape par étape. Ici, gardez la ligne Fixed. (corrigé) et ajoutez la ligne du billet 108 après celle-ci.

  2. Exécutez git diff --check et sh check.sh.

  3. Exécutez git add tickets.txt pour marquer le fichier comme résolu.

  4. Supprimez le stash, car sa modification se trouve maintenant dans votre fichier :

    Fenêtre de terminal
    git stash drop
    Sortie
    Dropped refs/stash@{0} (1b54611302688949f349718abca555a35e38fcb9)

Erreur courante

Le reflog ne contient pas ma modification

Symptôme : vous avez exécuté git restore tickets.txt ou git reset --hard, et perdu une modification. git reflog ne montre aucune entrée correspondante.

Cause : la modification n’a jamais été dans un commit ou un stash. Le reflog enregistre seulement les endroits où HEAD et les branches pointaient, et ces références pointent vers des commits. Git n’a jamais enregistré la modification. Il ne peut donc pas la récupérer.

Solution : Git ne peut pas vous aider ici. Cherchez une autre copie : l’historique d’annulation de votre éditeur ou sa fonction d’historique local. Pour éviter cela, faites un commit ou un stash avant toute commande qui supprime des modifications. Lisez aussi git status avant de l’exécuter.

Vérifiez votre compréhension

Prédisez : Amira exécute git reset --hard HEAD~2 dans son clone et perd deux commits locaux. Sam clone le dépôt distant sur son ordinateur. Peut-il trouver les deux commits d'Amira avec git reflog?

Afficher la réponse

Non. Le reflog est local à chaque clone et n’est jamais poussé. Amira n’a jamais poussé les deux commits. Ils existent donc seulement dans son clone. Le nouveau clone de Sam a son propre reflog, qui commence à sa création. Seule Amira peut récupérer les commits depuis son propre reflog, et seulement avant l’expiration des entrées.

Vérifiez votre compréhension

Vérification de fin : dans votre clone scratch, montrez que vous avez récupéré votre commit perdu et gardé celui de Sam. Quelle sortie prouve chaque partie?

Afficher la réponse
  • git log --oneline --graph -4 montre un commit de fusion avec deux parents : votre Add ticket 106 (ajouter le billet 106) et le Add the network printer contact de Sam.
  • tail -1 tickets.txt montre votre ligne du billet 106. tail -1 contacts.txt montre la ligne d’imprimante de Sam.
  • sh check.sh affiche PASS (réussite).
  • Vous n’avez rien poussé depuis scratch, et vous n’avez pas réécrit origin/main : git status indique une avance sur origin/main, et non une divergence ou un retard.

Récapitulatif

  • Le reflog enregistre l’endroit où HEAD pointait après chaque déplacement. Utilisez-le pour trouver l’empreinte d’un commit qui a quitté une branche.
  • Pour récupérer le commit, créez d’abord une branche sur son empreinte avec git branch rescue <hash>. Inspectez-le, puis fusionnez-le ou ramenez la branche à ce commit avec reset.
  • Une branche supprimée est une étiquette perdue. Recréez-la avec git branch <name> <hash>, en utilisant l’empreinte indiquée dans (was …) (était…) ou dans le reflog.
  • Le reflog est local, et ses entrées expirent. Il ne peut pas récupérer des modifications jamais enregistrées dans un commit ou un stash.
  • Un stash conserve temporairement les modifications sans commit. Sans -u, il ignore les fichiers non suivis. Il reste sur votre ordinateur et conserve l’entrée si pop (réappliquer et supprimer) provoque un conflit. Pour un travail plus long, faites un commit sur une branche.

C’est la fin du module 4. Vous pouvez maintenant résoudre un conflit, annuler un commit partagé avec revert et récupérer du travail local perdu. Dans le module 5, vous utilisez ces compétences pour préparer une livraison et la corriger.

Leçon suivante : Branches de version et étiquettes