4 points par GN⁺ 2025-01-18 | 1 commentaires | Partager sur WhatsApp
  • Comme il est difficile de chercher une solution dans Git si l’on ne connaît pas le nom du type d’erreur, cette ressource organise les commandes par scénarios de récupération courants pour pouvoir les retrouver et les utiliser immédiatement
  • Quand le dépôt a été gravement abîmé ou qu’il faut annuler une suppression ou une fusion erronée, on peut retrouver l’historique du travail avec git reflog puis revenir à un état antérieur avec git reset HEAD@{index}
  • Pour une petite correction ou un changement de message sur le dernier commit, git commit --amend permet d’aller vite, mais il ne faut pas l’utiliser sur des commits déjà poussés sur une branche publique/partagée
  • Si un commit a été fait sur la mauvaise branche, on peut le déplacer avec un enchaînement reset, stash, checkout, stash pop ou avec cherry-pick, mais la méthode sûre dépend du fait qu’il ait déjà été poussé ou non
  • reset --hard et git clean -d --force, qui forcent la remise en état selon le dépôt distant, peuvent aussi supprimer les fichiers non suivis : il faut donc vérifier avant de les exécuter

Pourquoi il est difficile de récupérer d’une erreur Git

  • Git se prête facilement aux erreurs, et pour corriger un problème il faut souvent déjà connaître le nom du concept nécessaire pour pouvoir le rechercher
  • Cette ressource regroupe les mauvaises situations fréquentes dans Git en anglais simple, avec des exemples de commandes
  • Ce n’est pas une référence complète, et il peut exister d’autres façons d’effectuer la même opération

Revenir à un état antérieur du dépôt

  • Si le dépôt a été gravement endommagé ou si vous voulez restaurer du contenu supprimé, consultez l’historique avec git reflog
git reflog


# affiche la liste des opérations Git sur l’ensemble des branches


# chaque entrée possède un index de la forme HEAD@{index}
git reset HEAD@{index}
  • reflog peut être utilisé dans les cas suivants
    • récupération de contenu supprimé par erreur
    • suppression d’une tentative qui a cassé le dépôt
    • récupération après une mauvaise fusion
    • retour à un point où tout fonctionnait réellement

Comment corriger le dernier commit

  • Si vous devez ajouter une petite correction juste après le dernier commit, utilisez git commit --amend --no-edit
# après les modifications
git add . # ou ajoutez des fichiers individuellement
git commit --amend --no-edit
  • Cette commande intègre les nouvelles modifications dans le dernier commit
  • On peut aussi créer un nouveau commit puis le fusionner avec rebase -i, mais amend est beaucoup plus rapide
  • Il ne faut pas utiliser amend sur des commits déjà poussés sur une branche publique/partagée
  • Pour ne modifier que le message du dernier commit, utilisez la commande suivante
git commit --amend


# modifiez le message du commit en suivant les instructions

Quand on a commité sur la mauvaise branche

  • Si vous avez commité par erreur sur master alors que cela devait être sur une nouvelle branche, créez une nouvelle branche à partir de l’état actuel puis supprimez le dernier commit de master
git branch some-new-branch-name
git reset HEAD~ --hard
git checkout some-new-branch-name
  • Cette méthode ne convient pas aux commits déjà poussés sur une branche publique/partagée
  • Si vous avez déjà tenté autre chose auparavant, il peut être nécessaire d’utiliser la forme HEAD@{number-of-commits-back} au lieu de HEAD~
  • Si le commit a été fait sur une branche totalement erronée, vous pouvez le déplacer avec l’enchaînement reset --soft, stash, checkout, stash pop
git reset HEAD~ --soft
git stash
git checkout name-of-the-correct-branch
git stash pop
git add . # ou ajoutez des fichiers individuellement
git commit -m "your message here"
  • On peut aussi utiliser cherry-pick dans la même situation
git checkout name-of-the-correct-branch
git cherry-pick master
git checkout master
git reset HEAD~ --hard

Annuler des modifications et d’anciens commits

  • Si vous avez modifié un fichier mais que git diff est vide, il est possible qu’il soit déjà indexé, auquel cas il faut utiliser l’option --staged
git diff --staged
  • Pour annuler des modifications datant de quelques commits, trouvez le hash avec git log puis exécutez git revert
git log
git revert [saved hash]
  • git revert crée un nouveau commit qui annule ce commit
  • Pour annuler seulement un fichier précis et non le commit entier, utilisez le flux consistant à remettre dans l’index une ancienne version du fichier puis à commiter
git log
git checkout [saved hash] -- path/to/file
git commit -m "Wow, you don't have to copy-paste to undo"

Forcer la remise en état selon le dépôt distant

  • Au lieu de supprimer le dépôt puis de le cloner à nouveau, vous pouvez utiliser des commandes Git pour le réaligner sur l’état du dépôt distant
git fetch origin
git checkout master
git reset --hard origin/master
git clean -d --force
  • git reset --hard origin/master aligne la branche sur l’état de master distant
  • git clean -d --force supprime les fichiers et répertoires non suivis
  • Il faut répéter checkout, reset et clean pour chaque branche endommagée
  • Ces opérations sont destructrices et irréversibles, il faut donc être prudent avant de les exécuter

Portée et contribution

  • Cette ressource n’est pas une référence Git complète, mais un recueil de procédures de récupération concrètes trouvées par essais et erreurs
  • Si vous voulez aider à ajouter des traductions, vous pouvez soumettre une PR sur GitHub

1 commentaires

 
GN⁺ 2025-01-18
Avis de Hacker News
  • Il y a quelques points que je changerais : j’utiliserais toujours git switch au lieu de git checkout, et j’éviterais autant que possible reset --hard
    Par exemple, pour « des commits qui auraient dû être sur une nouvelle branche mais ont été faits par erreur sur master », on peut traiter ça avec git branch some-new-branch-name, git switch -d HEAD~, git switch -C master, puis git switch some-new-branch-name
    Pour « un commit fait sur la mauvaise branche », je ferais aussi git switch vers la bonne branche, puis git cherry-pick master, git switch -d master~, git switch -C master
    Pour « tout abandonner et revenir à l’état distant », je composerais quelque chose comme git fetch origin, git restore -WS ., git clean -d --force, git switch -d origin/master, git switch -C master

    • Le modèle interne de Git est magnifique, avec ses blobs, arbres de commits et pointeurs de commits, mais le décalage avec l’interface en ligne de commande est énorme
      Même en comprenant bien le modèle de Git, ces recettes ne sont pas intuitives, et il faut en plus connaître les comportements particuliers de chaque commande
      Par exemple, git switch -c some-new-branch-name existe déjà, mais une commande comme git move-branch master HEAD~, qui déplacerait seulement une branche vers un autre commit, me semblerait plus intuitive
    • Je ne suis pas opposé à l’idée de privilégier git switch et git restore plutôt que git checkout, mais ces commandes ont été introduites dans Git v2.23, et cette version date d’environ 5 ans
      Les pages d’aide contiennent encore l’avertissement THIS COMMAND IS EXPERIMENTAL. THE BEHAVIOR MAY CHANGE.
      Bien sûr, cet avertissement existe pratiquement depuis l’apparition de la commande, et au bout de 5 ans il serait peut-être temps d’arrêter de la qualifier d’expérimentale
      Cela dit, git checkout reste plus rétrocompatible, et on peut considérer qu’il reflète l’usage de Git à l’époque où ce site web a été créé
      https://github.com/git/git/blob/757161efcca150a9a96b312d9e78...
      https://github.com/git/git/releases/tag/v2.23.0
      https://github.com/git/git/commit/4e43b7ff1ea4b6f16b93a432b6...
    • Enseigner par défaut git add . pour mettre des changements dans la zone de staging, ce n’est pas terrible
      Ajouter explicitement des fichiers précis laisse moins de chances de se dire plus tard « ah, zut »
    • Je serais curieux de connaître les raisons qui motivent cette recommandation
      J’aimerais savoir pourquoi switch est meilleur que checkout, et pourquoi il ne faudrait pas utiliser reset --hard
    • Je ne vois pas ce que signifie « éviter reset --hard »
      J’aimerais vraiment savoir pourquoi ce ne serait pas suffisant ; je l’utilise assez souvent avec alias git-restore-file='git restore --source=HEAD --', et ça semble bien fonctionner
  • Il faudrait commencer à recommander un client GUI comme voie par défaut pour apprendre Git
    Cela réglerait un tiers de ces problèmes, et un autre tiers n’apparaîtrait même pas
    Si, plus tard, on estime que la ligne de commande est plus rapide, on peut l’utiliser à ce moment-là ; mais les gens devraient d’abord voir visuellement comment on interagit avec l’arbre
    Personnellement, j’aime bien fork.dev, mais les clients actuels se ressemblent globalement

    • Je suis d’accord pour dire qu’une GUI offre souvent une meilleure expérience utilisateur avec Git
      Avec Magit, des actions comme « abandonner un cherry-pick » sont découvrables dans l’interface et utilisent le même raccourci que d’autres opérations « abandonner X »
      Si j’avais dû utiliser uniquement la ligne de commande Git, je n’aurais pas su par où commencer
      Il m’est aussi arrivé, pendant un rebase interactif, de supprimer un commit qu’il ne fallait pas supprimer ; comme un snapshot accessible depuis le reflog est créé à chaque début de rebase, il est sûr de revenir en arrière après un rebase raté
      L’UI de reflog de Magit est la même que son UI de log, donc je ne me suis pas perdu même en la voyant pour la première fois ; avec la CLI Git, je n’aurais probablement pas compris ce qui se passait
    • J’ai commencé à recommander jj comme alternative compatible avec Git
      J’aime le fait que, quand on exécute jj sans argument, il montre la partie pertinente de l’arbre de commits et l’emplacement actuel
      On voit d’un coup d’œil le commit sur lequel on travaille, la branche associée et l’historique, les autres branches actives du dépôt, ainsi que le message et l’état modifié ou non de chaque commit ; c’est excellent pour retrouver ses repères
    • Aux débutants avec Git, je conseille toujours de trouver l’UI qui leur convient et de l’utiliser plutôt que la ligne de commande
      Mais presque personne n’écoute, et ils finissent par galérer avec la CLI, casser des branches, prendre du retard par rapport à main et avoir peur de rebase
      Beaucoup de choses se résoudraient par glisser-déposer ou quelque chose d’équivalent dans une UI ; ça ressemble à une forme d’auto-flagellation
    • Si l’objectif est de voir visuellement comment interagir avec l’arbre, il existe un tutoriel interactif qui a aidé mes collègues : https://learngitbranching.js.org/
    • D’après mon expérience passée avec des frontends GUI de gestion de versions, tout va bien tant que tout fonctionne, mais dès qu’il faut démêler une situation compliquée, personne ne peut vraiment aider
      Même si l’interface est atroce, utiliser la même interface que le reste de l’équipe facilite la communication, et les ressources de dépannage en ligne supposent généralement la « méthode habituelle », ce qui les rend plus utiles
  • En avril 2025, Git aura 20 ans, et ça fait assez bizarre
    J’étais là à l’époque, et je me souviens m’être demandé lequel apprendre entre git, darcs et bazaar pour remplacer SVN ou CVS
    Je ne sais plus si j’avais aussi essayé Mercurial
    Je me demande si « l’effet GitHub » n’a pas, de fait, tué le besoin de nouveaux entrants dans le domaine des systèmes de gestion de versions
    Peut-être qu’à un moment donné, on avait suffisamment fini de tondre le yak

    • L’effet GitHub est réel, comme tous les effets de réseau, mais ça ne veut pas dire qu’aucune amélioration n’est possible
      Je suis passé complètement à jj, et comme c’est compatible avec Git, je n’ai pas besoin d’attendre que les autres changent aussi
      Je suis de plus en plus frustré par divers aspects de GitHub, donc j’espère qu’un jour tout le monde le dépassera aussi, mais de ce côté-là, le chemin à suivre ne semble pas aussi simple
    • SVN a toujours bien fonctionné
      SVN est intuitif et marche suffisamment bien dans 99 % des cas, au point qu’il n’y a pas besoin de « l’enseigner » spécialement aux gens
      J’aimerais qu’on arrête tous de jouer aux hackers 1337 et qu’on admette que Git est un outil disproportionné pour la majorité des gens
    • Toute amélioration devra au minimum être compatible Git, comme jj par exemple
  • Je pense avoir un assez bon modèle mental de ce que fait Git, mais dès que les commandes deviennent un peu complexes, je n’arrive plus à me souvenir des arguments
    Les commandes ne sont ni découvrables ni faciles à mémoriser
    Je ne sais pas si c’est l’interface texte qui est mauvaise, ou si c’est simplement difficile d’expliquer en texte la manipulation d’arbres

  • Je suis content de voir des résultats pour hg et Mercurial dans ce fil
    Je n’arrive toujours pas à croire que Mercurial ait perdu la guerre des gestionnaires de versions distribués, et je le considère comme un meilleur outil

    • Même aujourd’hui, Mercurial est sans conteste un meilleur outil
      Il est utilisable, stable, éprouvé en production et activement maintenu
      C’est un peu « sois le changement que tu veux voir », donc il suffit de l’utiliser
      Dire qu’il a perdu est très contre-productif, et l’inhibition implicite que ce genre de propos crée contribue à maintenir l’adoption de Mercurial à un faible niveau
      L’hébergement est possible au moins sur Sourcehut et heptapod.host
      J’exploite aussi une instance Heptapod personnelle : c’est un fork de GitLab avec prise en charge directe de Mercurial, et ça marche tout simplement bien
    • C’est intéressant, mais je me demande s’il y a un lien GitHub
    • Mercurial est un outil plus lent
    • Si vous avez besoin d’hébergement Mercurial, vous pouvez utiliser celui-ci : https://hg.reactionary.software/
  • Git est l’une de ces technologies qui, à bien des égards, ne suivent pas l’intuition, et qui n’ont jamais vraiment fini par rentrer dans ma tête
    À moins de l’utiliser depuis longtemps, il y a de fortes chances de devoir chercher sur Google ou consulter les pages man pour presque chaque opération

    • J’ai commencé avec SVN puis je suis passé à Git ; il a bien fallu apprendre les différences, mais j’ai trouvé que c’était devenu plus facile à bien des égards
      Surtout après avoir connu l’horreur d’essayer de gérer une grosse base de code dans SVN avec beaucoup de branches puis de les fusionner à nouveau, on voit que Git est absurdement meilleur sur ce point
      Pour quelqu’un qui arrive directement sur Git, la courbe d’apprentissage peut être bien plus raide, mais j’ai encore parfois du mal à comprendre où les gens bloquent
    • Hier, j’ai voulu supprimer un fichier de l’historique
      Avec la méthode intégrée, filter-branch, on obtient, après un long délai, un avertissement disant d’appuyer sur Control+C et de télécharger un script Python tiers appelé filter-repo
  • Je n’en suis pas fier, mais mon opération Git « c’est foutu » la plus fréquente consiste à supprimer le dépôt local, le recloner, puis réappliquer mes modifications
    Dans 95 % des cas, ça marche très bien ; pour le reste, je demande de l’aide à la personne chargée du DevOps

    • J’utilise Git depuis presque 15 ans, et j’ai même créé deux fois des programmes/produits qui utilisaient Git en interne pour produire un résultat précis, mais il m’arrive encore de faire un truc du genre cp -R .git ../git-backup avant d’entreprendre quelque chose d’un peu risqué dans Git
      Comme ça, si je casse trop de choses, je peux simplement remplacer tout le répertoire .git par l’ancienne copie
      C’est beaucoup plus rapide que de trouver comment annuler correctement une opération précise ou un ensemble d’opérations
    • Tu n’es pas seul : https://xkcd.com/1597/
  • Ce que j’aimerais vraiment que les gens intègrent à propos de Git, c’est que Git est un stockage de données append-only
    Pour mettre du travail dans le dépôt, il suffit de committer, et une fois qu’il y est, aucune commande Git ne peut le supprimer
    Le reflog fonctionne aussi sur ce principe
    Quoi que vous fassiez, vous pouvez revenir à un ancien état de branche et restaurer le travail depuis le stockage de données
    Mais si vous ne committez pas, Git ne peut pas vous aider, donc il faut simplement committer
    Committez toujours, et si la charge est trop lourde ou si vous oubliez sans cesse, il faut corriger vos outils
    Si vous écrivez du code toute la journée, en général vous devriez committer au moins une fois par heure

  • C’est un détail, mais chaque fois que je vois git reflog, je le lis comme re-flog
    C’est peut-être pour ça que j’ai du mal à mémoriser cette commande, et que je ne pense pas instinctivement à « reference log »
    À la place, j’ai l’impression de demander à Git de brader quelque chose une deuxième fois

  • Je ne suis pas utilisateur de Git, mais quand je vois ce genre de choses, je suis de plus en plus convaincu que git commit en soi n’a pas de sens, et que seuls les moments où le commit est pushé ou mergé comptent
    C’est un peu comme enregistrer un fichier texte
    On n’écrit pas un court message à chaque fois qu’on enregistre un fichier, donc on peut simplement passer outre

    • Si le changement vient au terme d’un pénible parcours de débogage et que d’autres personnes risquent de vouloir s’y référer plus tard, alors on écrit un message
      Pas forcément court : plus le changement est petit, plus l’explication peut parfois être longue
      Il m’est déjà arrivé d’écrire 2 ou 3 paragraphes pour un changement de 2 ou 3 lignes à fort impact
    • Il me paraît évident que les commits locaux et les commits distants n’ont pas le même objectif
      Un commit local est une manière de créer un point de contrôle à un moment où tout compile et fonctionne
      Cela permet de mettre ses changements de côté avec stash, ou de revenir à un état qui fonctionnait si on a tout cassé
      Avant de pousser, on peut faire un reset et découper en commits appropriés, ce qui garde l’historique distant propre et évite qu’il devienne un tas de wip
      Ou bien on peut faire un squash merge de la PR pour obtenir les avantages des deux côtés
    • Un commit est très important, car c’est l’opération qui amène Git à créer en interne une copie des fichiers
      En revanche, inutile de saisir un message vide de sens comme more fixes
      En général, on crée un commit initial du type (WIP) nom de la fonctionnalité, on enchaîne les commits amend jusqu’à être satisfait, puis on retire (WIP) du message à la fin
    • On le compare à l’enregistrement d’un fichier texte, mais beaucoup de gens écrivent aussi un court texte sur leur journée avant de dormir
      Je vois le journal des révisions Git comme une sorte de journal intime similaire
      Il est plus structuré qu’un document TODO séparé et incite à expliquer de manière lisible ce qui a déjà été fait
      Quand il faut décider jusqu’où revenir en arrière, il peut être utile de savoir ce qu’on avait en tête au moment du commit, et si l’approche était mauvaise, cela aide aussi pour l’analyse a posteriori
      Si un travail qui n’a pas été mergé ou pushé est organisé en changements séparés avec un objectif, il devient beaucoup plus facile de ne conserver que les bonnes parties quand certaines sont bonnes et d’autres mauvaises
    • Jusqu’au push, tout est local, donc on peut faire comme on veut
      Cela dit, techniquement, les commits ne sont pas dénués de sens
      Sans commit, il n’y a rien à push ou à merger
      Même d’un point de vue non technique, consigner en texte clair pourquoi on change quelque chose est très utile
      Si l’on se retrouve à écrire ce genre d’explication trop souvent, il faut réduire l’unité de « changement » ou être plus sélectif sur les choses que l’on touche en même temps
      Si l’on n’utilise pas Git, je me demande avec quoi on fait la gestion de versions et la gestion des changements