Git et son histoire

Comme nous l'avons vu dans merge vs rebase, il est préférable d'avoir un historique propre qui raconte l'évolution du produit que celui de nos modifications de code . Ca ne sert pas à grand chose de savoir qu'on a dû modifier 3 fois le fichier de conf avant de mettre les bons paramètres. Il n'est pas n'ont plus utile de voir qu'on a dû faire une correction d'un ancien commit pour qu'il fonctionne.

Par contre :

  • il faut faire des commits fonctionnels (le code compile, les tests unitaires sont ok, ...).
  • il faut que les commit soient atomiques pour qu'on puisse les récupérer si besoin ( pour mettre sur une autre branche par exemple).
  • Un commit peut être manipuler unitairement par les fonctionnalités de git ( déplacé, fusionné, revert d'un commit, ...)

Mais ceci est vrai pour faire la merge-request pour que la branche principale contienne bien des commits qui raconte ce qui a été fait pour faire évoluer le produit.

Ceci n'est pas forcément vrai pendant que l'on est dans l'action, dans le développement. On essaie de faire des commits propres mais on ne peut rarement le faire du premier coup.

Exemple:

  • On fait un commit puis on s'aperçoit que l'on a oublié un fichier.
  • On est obligé de commiter pour tester puis ensuite on fait un ou des commit de correction de fichier
  • On veut refactorer améliorer un bout de code mais on veut garder le code de départ pour pouvoir revenir en arrière
  • ...

Nous allons voir ce que propose git pour palier à ce problème ou pour simplement organiser ou identifier le but d'un commit.

Ce que nous allons voir ensuite permet au développeur d'avoir la flexibilité de faire autant de commit qu'il le souhaite tout en donnant les informations nécessaires à git pour qu'il puisse ensuite les réorganiser automatiquement et /ou au développeur de le faire soit même pour avoir finalement un historique clair, simple et concis (en respectant certaines règles pour éviter autant que faire ce peu des conflits)

1 Modifier un commit

1.1 Modifier le dernier commit avec perte de donnée

Etude de cas : j'ai oublié d'inclure un fichier dans mon commit

git commit --amend -m "option -m facultatif seulement si ça engendre un changement du message du commit"

Celle si est destructive car elle va remplacer l'ancien commit par un nouveau commit qui ajoute la modification de ce commit. Pas de conflit car on modifie le dernier commit donc pas de conflit possible On ne peut pas faire push directement car le remote à un commit qui n'est pas dans le local du coup on a le message

Your branch and 'origin/MNSPF-Matrix-Editor-Init' have diverged and have 1 and 1 different commits each, respectively.

il faut donc faire un force push pour remplacer également le commit remote par le nouveau. Après le push l'ancien commit n'existe plus et donc impossible de revenir sur l'ancien commit une fois le push effectué

1.2 Modifier un commit sans perte de donnée

Etude de cas : on test et on corrige un bug sur un fichier d'un des commit que l'on a fait. Donc le mieux serait de modifier ce commit.

git log --pretty=oneline --abbrev-commit --graph --decorate -10

On récupère le sha1 du commit que l'on voudrait modifier

git commit --fixup=<sha1>
On réaffiche la liste des commits on voit bien que le commit est un nouveau commit qui ne modifie ni le dernier commit, ni le commit qu'on lui a donné en source. Il suffit donc de supprimer ce dernier commit pour revenir en arrière

2 Merge-request

La bonne pratique est qu'avant la merge-request il faut rendre sa branche avec tous ces commits de dev dans un état prêt à être pousser sur la branche de release. C'est à dire avec un minimum de commit qui sont simples, atomiques et concis.

Pour cela, git est votre ami et surtout le rebase interactif.

2.1 On lance la commande:

git rebase -i --autosquash HEAD~24
où 24 signifie que l'on souhaite réorganiser les 24 dernier commit

2.2 git ouvre alors un vi (vim) qui vous montre les commits et ce qu'il est sensé faire avec chacun d'eux .

Et l'on voit de suite qu'il a réorganisé automatiquement les fixup tel qu'on lui avait dit grâce à l'option --autosquash (fixup est un squash dont on ne veut pas modifier le message, et un squash est la fusion du commit dans le précédent : voir l'affichage de git en dessous des actions qu'il va faire)

2.3 Si l'on valide il va donc fusionné les fixup dans les commits précédents et on va avoir un nouveau historique de commit.

2.4 Les commits fusionnés étant des nouveaux il faut faire attention car le push se fait en force ... et cette fois le retour arrière sera plus possible.

Mais on peut aussi modifier manuellement des commits, les réorganiser à l'étape 2. Souvent à ce stade on ne modifie plus les commits mais on peut les réorganiser et les fusionner manuellement :

  • On déplace les lignes
  • On supprime la ligne d'un commit inutile
  • On marque une ligne squash/fixup pour qu'il fusionne le commit avec le précédentn (et ouvre une fenêtre pour modifier le commentaire ppour le squash).

Attention conflit !!!

Toutes ces actions peuvent avoir des incidences et des conflits plus ou moins facile à résoudre d'où le fait de le prévoire en amont pour savoir ce qu'on peut faire ou pas.

Exemple :

  • Etat initial image.png

  • git rebase -i HEAD~12 image.png

On voit que l'on a fait plusieurs commit pour la même chose en faisant plusieurs corrections

  • Nous allons donc faire des fixup et finir par un squash pour modifier le commentaire image.png

  • On valide les modifications le rebase s'execute jusqu'au squash ou il demande de modifier le commentaire comme prévu image.png

On voit que git ajoute des commentaire pour expliquer tout ce que comprend ce commit

  • Après confirmation du commentaire, le rebase se termine et on obtient ce nouveau historique simplifié image.png

Ajouter un exemple avec git commit --fixup et --autosquash...

3 Eviter les conflits : bonnes pratiques

  • On fait un rebase interactif sur une branche push à jour : pour pouvoir revenir quand on veut à l'état initial (surtout si l'on va au bout du rebase avant de s'apercevoir que les merge ne se sont pas bien passé.
  • On évite de modifier des fichiers identiques dans des commits différents : il faut penser commit lorsqu'on développe
  • Lorsqu'on fait un fixup il faut bien identifier les commits que l'on saute pour identifier des conflits et être sûr de pouvoir les résoudre
  • A compléter avec les problèmes rencontrés