Git est un outil de versionnage très utile, voici comment je m'en sers.

À noter que ce guide est destiné aux personnes qui ont déjà utilisé git auparavant, il ne s'agit pas ici d'un tutoriel pour débutants (bien que je sois ici assez exhaustif dans mes explications).

TL;DR

Les fondations

Vous êtes bien installé devant votre écran, les doigts ne demandent qu'à furieusement taper les touches de votre clavier, mais avant le top départ, il faut préparer le terrain ! Voici quelques commandes primordiales (pour ne pas dire obligatoires) à réaliser avant de se lancer dans un projet.

Premièrement, je n'aime pas la manière qu'a git de faire des commits de fusion lors d'un pull qui se passe mal (divergence entre votre version locale et distante par exemple, aka. vous avez committé et quelqu'un d'autre aussi en même temps). Nous allons donc demander à git de ne pull que lorsque l'historique est linéaire (fast-forward), et sinon de faire automatiquement un rebase :

git config pull.rebase true

En cas de divergence, un simple git rebase origin/branche_concernée vous sortira d'affaire !

Ensuite, il nous arrive de vouloir avoir une vue plus graphique de notre historique git. Voici un alias, à placer dans votre ~/.gitconfig, qui vous permet d'afficher un graphique de vos branches et vos commits de manière plus visuelle que le git log de base de git (honteusement volés ici) :

[alias]
  lg1 = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(bold yellow)%d%C(reset)' --all
  lg2 = log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n''          %C(white)%s%C(reset) %C(dim white)- %an%C(reset)' --all
  lg = !"git lg1"

Si vous n'avez pas besoin d'autant de décorations, ou que vous ne pouvez pas définir d'alias, cette commande fait quelque chose d'assez similaire :

git log --all --decorate --oneline --graph

Pour avoir des messages de commits unifiés et cohérents avec de jolis alias :

fix = "!f() { git commit -sm \"fix: $1\"; }; f"
feat = "!f() { git commit -sm \"feat: $1\"; }; f"
misc = "!f() { git commit -sm \"misc: $1\"; }; f"
wip = "!f() { git commit -sm \"wip: $1\"; }; f"

Des branches, des branches, et encore des branches

Quand on travaille à plusieurs sur git, il devient presque obligatoire d'avoir des branches pour éviter de se sentir "à l'étroit" dans son repo. Pour pouvoir collaborer et avancer sur un projet en groupe sans se marcher sur les pieds les uns des autres, les branches deviennent l'outil indispensable de git.

J'utilise les branches de la manière suivante :

  • les branches ont une durée de vie très courte
  • une fois que la branche a rempli son rôle et qu'elle a été fusionnée, elle doit être supprimée
  • une branche par fonctionnalité ou par résolution de problème
    • les noms des branches sont libres tant qu'ils sont clairs
    • c'est mieux d'avoir comme nom la fonctionnalité développée ou son rôle
    • lorsqu'une branche sert à développer une résolution de bug ou un fix, il est mieux de l'inclure dans le nom
    • lorsque vous travaillez à plusieurs sur la même fonctionnalité, il est conseillé de faire une branche par personne en partant de la branche princpale de cette fonctionnalité

Évidemment, une branche fait exception : master (ou main, peu importe), qui est une version "propre", "stable", et qui doit présenter le projet dans un état fonctionnel, tel qu'il serait montré à un client ou à un responsable.

Il est possible d'avoir une seconde branche sur les projets plus importants, qui se nomme alors develop et qui est la version en développement actif d'un projet, mais qui ne peut pas être fusionnée sur la branche princpale pour des raisons de stabilité par exemple, ou qui n'est pas prête pour la mise en production.

Pour créer une branche fille à partir de la branche sur laquelle vous êtes :

git checkout -b nom_de_la_nouvelle_branche

Et pour basculer de branche en branche :

git checkout nom_de_la_branche

Si vous ne savez pas sur quelle branche vous vous trouvez, vous pouvez faire :

git branch

Rebaser, c'est la clef

Cette partie peut être ignorée si vous ne pouvez pas réaliser de push force (ou équivalent). En effet, un rebase modifie fondamentalement l'historique de votre git. Si vous n'êtes pas sûr de vous, n'en faites pas. Je ne suis pas responsable si vous perdez des données.

Votre fonctionnalité est développée et testée, vous voulez la fusionner avec votre branche parente ? Vous voulez récupérer ce qui est sur votre branche parente et que vous aimeriez récupérer sur la votre ? Il est temps de rebase !

Lorsque l'on crée une branche, celle-ci a un commit de "départ", depuis lequel elle se base pour faire du versionnage en parallèle des autres branches. Parfois il est nécessaire de récupérer du travail qui a été mis sur la branche mère dans notre branche de travail, par exemple la résolution d'un bogue handicapant.

Un rebase va faire changer votre commit de départ et l'échanger avec un autre. Prenons le schéma suivant :

Schéma d'un rebase

Nous avons ici la branche new_feature qui part d'un commit de master. Disons que vous êtes en train de développer une fonctionnalité importante, et qu'un bug vous ralentit dans l'exécution de vos tests, mais qu'il a été réglé après la création de votre branche, et qu'il est disponible sur master. Pour le récupérer sur votre branche, vous n'avez qu'à rebase votre branche sur la dernière version de master. La commande pour faire ceci serait alors :

git rebase nom_de_branche_parente

Il est important de noter que vous risquez d'avoir des conflits au cours de cette opération. En cas de conflit, vous pouvez les régler, puis faire git add fichier_en_conflit et enfin git rebase --continue. Cela aura pour effet de réécrire l'historique git de votre branche, d'où l'importance de faire un push en force, puisque votre version locale et la/les version(s) distante(s) n'auront plus rien à voir.

Quel rapport avec ce git flow

Comme dit plus haut, les branches ont une existence précise et bien définie dans le temps. Une branche n'a pas a être fusionnée dans sa branche parente tant que son contenu n'est pas complet, ou au moins utilisable. Cela ne fait pas vraiment de sens de "polluer" une branche parente dans le seul but d'en récupérer le nouveau contenu. Les opérations de rebase sont donc ici fréquentes.

Fusion non préparée = fusion refusée

Votre fonctionnalité ou correctif est terminé(e), votre branche est prête à être fusionnée ? En vérité, pas tout à fait. Il faut maintenant préparer la fusion !

Afin de faciliter au maximum la fusion de votre branche, il va falloir faire un dernier rebase. Le but de ce rebase est ici de vérifier que le rôle de la branche et du contenu qu'elle propose sont toujours fonctionnels et qu'aucun bogue n'a été intruduit par les nouveautés disponibles sur la branche parente. Cela va aussi vous permettre de gérer les conflits avec la branche mère au sein même de la votre. Une fusion n'introduira donc pas de bogue, et vous restez pleinement responsable de votre branche.

Enfin, vous n'êtes pas responsables de la fusion, un collègue ou responsable va d'abord vérifier votre code et le contenu que vous apportez et approuver (ou rejeter) la fusion que vous demandez. Grâce aux opérations de rebasage successifs que vous avez fait durant la durée de vie de votre branche, la fusion se fera sans encombre.

À noter que si vous avez des outils tels que les Merge Requests chez GitLab, ou Pull Requests chez GitHub, n'hésitez surtout pas à les utiliser ! Ces outils permettront de garder un suivi des fusions, et d'expliquer son but à un responsable qui ne les a pas forcément toutes en tête.

Pour fusionner une branche dans celle sur laquelle vous vous trouvez :

git merge nom_de_la_branche_à_fusionner_dans_celle_sur_laquelle_vous_êtes

Au revoir vieille branche

Les opérations décrites ici peuvent entraîner une perte de code. Je ne suis pas responsable si vous supprimez une branche avant qu'elle n'ait été fusionnée. Ne supprimez une branche que si vous êtes sûr qu'elle n'a plus d'utilité.

Votre branche est désormais fusionnée ? C'est la fin pour elle, il est désormais temps de lui dire au revoir !

Commençons d'abord par la supprimer sur le repo distant :

git push -d origin nom_de_branche

Ici, le serveur distant s'appelle origin, comme c'est le cas la plupart du temps.

Vos collègues peuvent synchroniser leurs branches et faire disparaître leurs références locales à des branches fantômes distantes avec la commande git fetch -p (le -p étant ici pour "prune").

Enfin, il est temps de supprimer votre copie locale de cette branche :

git branch -d nom_de_branche

À noter que le -d ne supprimera votre branche que si elle a été fusionnée. Il est possible de forcer la suppression d'une branche locale non fusionnée avec l'option -D qui équivaut à un git --delete --force sur la branche.

"Don't cry because it's over, smile because it happened." - Dr. Seuss

TL;DR

On commence par demander à git de ne pull que s'il n'y a pas de divergence avec la version distante de notre branche, et sinon, de rebase notre branche automatiquement :

git config pull.rebase true

Ensuite on utilise une branche par fonctionnalité et par correctif. Une branche a un rôle précis et une durée de vie très courte. Lorsque plusieurs développeurs travaillent sur la même branche, ils ne doivent pas exclure la possibilité de créer des branches personnelles.

master est en tous temps propre et présentable. Une branche intermédiaire peut être utilisée pour éviter de polluer inutilement la branche principale. Elle pourra s'appeler develop par exemple.

Les rebase sont fréquents et servent à récupérer sur une branche fille du nouveau contenu de sa branche mère.

La fusion se déroule sans accroc, un rebase doit être fait avant pour gérer les conflits sur la branches fille.

Une fois la branche fusionnée, elle doit être supprimée.

Informations supplémentaires

Vocabulaire

  • repo : raccourci de repository, qui est l'environnement de travail de git, c'est là dedans que l'on créé ses branches, et que l'on versionne son travail.

Pour aller plus loin

  • Le fonctionnement des objets, commits et arbres sur git (en profondeur) : vidéo.
  • Le fonctionnement des branches sur git (en profondeur) : vidéo.
  • Comment git stocke son arborescence de fichiers : article.
  • Les commandes associées à git branch : documentation.