Git merge et git rebase: les éternels incompris

Soumis par GoZ le jeu 19/05/2016 - 17:41

Git merge et git rebase: les éternels incompris

Merge, rebase, c'est quoi le problème ?

Avec git, il y a deux manières de regrouper le travail de deux branches :

  • git merge : git rassemble et joue tous les commits. En cas de conflits, il laisse tout en plan et on doit se débrouiller pour les résoudre. Il faudra ensuite faire un commit de fix. Généralement, le merge génère un commit.
  • git rebase : reconstruit l’arbre. C’est à dire que si l’on est actuellement sur la branche "enfant" et que l’on fait un git rebase "parent", git va se placer sur le dernier commit de recette et jouer tous les commits qui ont été fait sur dev depuis la dernière fois où il était a jour avec recette. Il déroule les commits un par un, s’arrête dès qu’il y a un conflit sur le commit, ce qui permet de corriger ce commit la uniquement, de le modifier directement puis de continuer sur le prochain etc.

Arborescence git merge polluées par les commits de merge
Arborescence git polluées par les commits de merge

git merge va donc générer beaucoup de commits inutiles qui vont rendre inexploitable l’arbre d’historique.
git rebase va conserver une arbre linéaire et lisible ce qui permettra de la maintenir facilement, de pouvoir lire les modifications qui ont été faites, l’état entre les différentes branches etc.

Quand faire un git merge et quand faire un git rebase ?

La méthode est assez simple, il faut toujours se représenter les branches sous forme d’une arborescence. Il y a toujours une branche parente (exceptée master qui est la toute première) et ses branches enfants. Lorsqu’il s’agit de récupérer le contenu d’un environnement parent : exemple récupérer le contenu de la branche parente vers sa branche enfant, on se positionne sur la branche enfant, et on fait un git rebase parent.

Si la branche enfant est jugée ok et que l’on souhaite faire remonter ses modifications vers la branche parente, on se place sur la branche parente puis on fait un merge de l’enfant. (git merge enfant). Attention toutefois, il faut forcement que la branche enfant ait tous les commits de la branche parente. Le mieux est donc de s’en assurer en se plaçant au préalable sur la branche enfant, de faire un rebase de son parent puis de se placer sur la branche parente et de faire un merge de sa branche enfant.

Pour clarifier un peu plus les choses :

Si on veut passer du code de parent vers enfant : on rebase :

# On se place sur la branche enfant
git checkout enfant
# On reconstruit enfant en prenant tous les commits du parent puis on rejouant ses propres commits. L’option -i permet de gagner en lisibilité en voyant tous les commits de la branche enfant qui seront joués.
git rebase -i parent
# On pousse vers le repo (en général, après un rebase, vu que l’on a reconstruit/réécrit l’arbre, il faut forcer le push)
git push origin enfant --force

Si on veut pousser les modifications de enfant vers parent : on merge :

# On commence par la procédure précédente pour être certain que la branche enfant est à jour par rapport à la branche parent.
git checkout enfant
git rebase -i parent
git push origin enfant --force
# On se place sur la branche parent
git checkout parent
# On merge le contenu de l’enfant vers parent
git merge enfant
# On met a jour le repo
git push origin parent

Pull

Dernier point lorsque l’on travaille à plusieurs sur un projet, le git pull.

Par défaut, pour tous les logiciels et la ligne de commande le git pull utilise un principe de merge. Il est généralement possible de configurer ces logiciels pour que le git pull se base sur un rebase et non un merge.
Pourquoi ? Car il faut toujours partir du principe que l’on doit avant tout récupérer ce que les autres ont fait (distant/parent), puis rejouer nos propres commits (que l’on maitrise et connait). C’est beaucoup plus simple ainsi pour gérer les conflits.

Des commits de merge ne font que rendre plus incompréhensible les modifications du projet.

Si vous ne trouvez pas l’option en question, la commande suivante fait le travail :

git pull —rebase

Finalement

J’ai de nombreux exemples de projets où une mauvaise gestion de merge/rebase a abouti à plusieurs jours de perdus à gérer les conflits, puis re-gérer les mêmes conflits plusieurs fois, voir même à fini par devoir perdre l’historique car la gestion de conflits perpétuels devenait ingérable (un commit de merge avec conflit va embarquer dans son commit TOUTES les modifications depuis la dernière fois où les deux branches étaient à jour).

Il est donc important de conserver un historique de commit clair et de passer un peu plus de temps a le maintenir régulièrement plutôt que de s'arracher les cheveux plus tard.

Aller plus loin

Pour ceux qui voudraient approfondir le sujet, un article très complet (vraiment très complet, attention à ne pas vous perdre ou vous faire des noeuds au cerveau) http://www.git-attitude.fr/2014/05/04/bien-utiliser-git-merge-et-rebase/.