Retrouver son historique Git

Sherlock Holmes

En discutant avec un collègue, je me suis rendu compte que j’avais une compréhension erronée, de la "véritable nature" des branches et des tags dans Git.

En effet, je me les représentais comme des "ensembles de commits". C’est peut-être la manière usuelle de les décrire qui m'a induit en erreur.

Cette branche "contient" ces commits.

Ce commit "provient de" cette branche.

En réalité, les tags et les branches sont simplement des pointeurs vers des commits.

Chaque commit connaissant son parent, n’importe quel commit est indirectement auto-porteur de son historique.

On peut donc utiliser les commandes usuelles directement avec les hash des commits.

Note : pour le confort de la lecture, je remplace les hashs de commit par des numéros.

git checkout #1

git diff #2 #3

git rebase #4

On peut légitiment se questionner sur l'intérêt de manipuler directement des commits via leur hash, alors qu'on dispose d'outils plus "amicaux", à savoir les branches et les tags. Au quotidien, cela ne s'impose pas. Mais occasionnelement, cela peut permettre de récuperer de l'historique que l'on croyait perdu.

Restaurer une branche / un tag supprimé

Prenons en exemple, un dépôt avec deux branches : master qui pointe sur le commit #1 et feature qui pointe sur le commit 3.

(master)$ git log --oneline --all
#3 (feature) update feature
#2 add feature
#1 (HEAD -> master) init readme

Note : l’option all permet d'afficher tous les commits "attachés", pas uniquement à la branche master où l'on se trouve ; HEAD indique notre position.

En supprimant la branche feature, les commits #2 et #3 deviennent orphelins ; ils n’apparaissent plus dans git log.

git branch -D feature

(master)$ git log --oneline --all
#1 (HEAD -> master) init readme

Orphelins certe, mais pas perdus. En se positionnant directement sur le commit #3, on retrouve son historique.

(master)$ git checkout #3

(#3)$ git log --online
#3 (HEAD) update feature
#2 add feature
#1 (master) init readme

Libre à nous de re-créer la branche feature dans le même état qu'avant sa suppression.

(#3)$ git branch feature

(#3)$ git checkout master

(master)$ git log --oneline --all
#3 (feature) update feature
#2 add feature
#1 (HEAD -> master) init readme

Bien sûr, après avoir supprimé la branche feature, on peut ne pas connaître le hash du dernier commit. Dans ce cas, on consulte l'historique des déplacement de HEAD via git reflog.

(master)$ git reflog | grep 'update feature'
#3 HEAD@{1}: commit: update feature

La démarche est exactement la même pour restaurer un tag supprimé.

Restaurer un commit écrasé

Reprenons l'exemple de notre branche feature, qui pointe sur le commit #3.

(feature)$ git log --oneline
#3 (HEAD -> feature) update feature
#2 add feature

En éditant le dernier commit #3, celui-ci est en réalité remplacé par un nouveau #4.

Au passage, il n'est pas recommandé de modifier son historique. Il est toujours sécuritaire d'empiler un autre commit pour éditer / compléter des modifications.

(feature)$ git commit --amend

(feature)$ git log --oneline
#4 (HEAD -> feature) update feature edited
#2 add feature

Le commit #3 a disparu des radars, mais n'est pas perdu, seulement orphelin. En se positionner directement dessus, puis en forçant la création de la branche feature (qui existe déjà), on peut le restaurer.

(feature)$ git checkout #3

(#3)$ git log --online --all
#4 (feature) update feature edited
#3 (HEAD) update feature
#2 add feature


(#3)$ git branch feature --force

(#3)$ git checkout feature

(feature)$ git log --oneline
#3 (HEAD -> feature_branch) update feature
#2 add feature

La documentation officielle de Git pour ~démolir~ modifier puis récupérer son historique.

Finalement, ils ne sont pas si antipathiques ces commits. :)