Git Workflow
Cette page est une traduction et une synthèse de l'article de Vincent Driessen dans A successful git branching model
Cette traduction ne reflète pas le modèle pratiqué au sein d'Elosys mais sert de point de départ à la compréhension d'un workflow git.
En travaillant avec git, chaque collaborateur a un dépôt. Dans les
faits, tout dossier contenant un sous-dossier .git
est un dépôt.
Dans le modèle centralisé, un dépôt central reçoit les commits des collaborateurs pour les rendre disponibles à l'ensemble des participants du projet, mais ce n'est pas le seul moyen d'échanger ses commits.
Dans ce qui suit dépôt
ou repo
désignera la copie locale de
chaque collaborateur.
Dépôt central ou central repos
désignera le dépôt qui sert de
point d'échange entre tous les intervenants. C'est par exemple ce
que nous que nous désignons par une adresse du type
git@git.elosys.net:group/projet.git
.
Les branches principales
Dans un modèle centralisé le dépôt central
contient deux branches
principales avec une durée de vie illimitée :
master
develop
Du point de vue du collaborateur ces deux branches correspondent au résultat de la commande
git branch -r
origin/master
origin/develop
On va considérer origin/master
comme branche principale où le
code source reflète l'état production-ready.
On va considérer origin/develop
comme branche principale où le
code source reflète les derniers changements en vue de la prochaine
release
.
Quand le code source de la branche develop atteint un point de stabilité et est prêt pour une release tous les changements qui y ont été apportés doivent être mergés dans master puis taggé avec un numéro de release.
Ainsi, quand tous ces changements sont mergés dans master, cela représente de fait une release de production.
Nous aurons tendance a être strict sur ce point car théoriquement nous pourrons utiliser un Git hook pour automatiser un build ou un déploiement sur un serveur de production à chaque fois qu'il y a un commit sur master.
Les branches de support
Les branches de support sont là pour aider aux développements parallèles entre les membres des équipes, faciliter le suivi des releases, préparer les releases de production et aider aux dépannages rapide des problèmes en production.
Contrairement aux branches principales, ces branches ont une durée de vie limitée puisqu'elle seront éventuellement effacées.
- Feature branches (branches de fonctionnalité)
- Release branches (branches de release)
- Hotfix branches (branches de dépannages à chaud)
Chacune de ces branches a un objectif précis et des règles strictes.
Feature branches (branches de fonctionnalité)
- Démarrent depuis : develop
- Retournent à : develop
- Nommées : tout sauf master, develop, release-\* ou hotfix-\*
Appelées aussi topic branches
, les feature branches sont
utilisées pour développer de nouvelles fonctionnalités pour une
plus ou moins proche release.
Quand on commence à développer une nouvelle fonctionnalités la
release cible (target release
) dans laquelle cette fonctionnalité
sera incorporée peut être connue.
La feature branch (branche de fonctionnalité) existe tant que la fonctionnalité est en développement.
Elle peut soit être mergée dans la branche develop
pour
définitivement ajouter cette fonctionnalité dans la prochaine
release soit abandonnée en cas d'expérience déroutante.
Typiquement, les branches de fonctionnalité (feature branches)
demeurent dans les dépôts des développeurs seulement et on ne les
retrouvent pas dans origin
.
Création d'une branche de fonctionnalité
On démarre toujours une branche de fonctionnalité depuis la branche develop
$ git checkout -b mafonctionalite develop Switched to a new branch "mafonctionalite"
Incorporer une fonctionnalité achevée dans develop
$ git checkout develop Switched to branch 'develop' $ git merge --no-ff mafonctionalite Updating ea1b82a..05e9557 (Summary of changes) $ git branch -d mafonctionalite Deleted branch mafonctionalite (was 05e9557). $ git push origin develop
L'option –no-ff
va obliger le merge a créer un commit de merge même
si le merge aurait pu se faire en fast-forward. Cela permet d'éviter
de perdre l'information de l'existence d'une branche de
fonctionnalité.
Sans l'option –no-ff
il est impossible de voir dans l'historique de
Git quel est la série de commits qui a introduit la fonctionnalité.
Défaire ces commits ultérieurement serait très difficile.
Cela va bien sûr créer des commits vides mais le gain en vaut le coup.
Release branches (branches de release)
- Démarrent depuis : develop
- Retournent à : develop ET master
- Nommées : release-\*
Les branches de release sont là pour la préparation d'une release de production. C'est la branche où on fait les derniers peaufinages : dépannage de bugs mineurs, réglages des métadata de la release (le numéro de version, la date de sortie etc).
En faisant cela sur une branche de release, la branche develop est nettoyée pour recevoir les fonctionnalités pour la prochaine grande release.
Le moment clé pour faire sortir une branche de release depuis la branche develop est quand cette dernière reflète l'état désiré pour la prochaine release.
Tout au moins toutes les fonctionnalités (features) qui ont été ciblées pour cette release doivent être mergées dans develop à ce moment.
Toutes les fonctionnalités qui ont été ciblées pour une release postérieure doivent attendre que la release branche soit sortie (branched off)
C'est exactement au début d'une release branch que la prochaine release se voit attribuée un numéro de version et pas avant.
Jusqu'à ce moment la branche develop reflète les changements pour la prochaine release mais il n'est pas encore décidé si cette prochaine release sera une 0.3 ou une 1.0 jusqu'à ce que la branche de release soit démarrée.
La décision est fait au début de la branche de release et dépend des règles du projet sur la numérotation de versions.
Création d'une branche de release
Les branches de release sont créées à partir de la branche develop. Admettons, par exemple, que la version de production actuelle est 1.1.5 et que nous avons une grande release à venir. L'état de la branche develop est prêt pour la prochaine release et nous avons décidé que le numéro de version sera 1.2 (au lieu de 1.1.6 ou 2.0).
$ git checkout -b release-1.2 develop Switched to a new branch "release-1.2" $ ./bump-version.sh 1.2 Files modified successfully, version bumped to 1.2. $ git commit -a -m "Bumped version number to 1.2" [release-1.2 74d9424] Bumped version number to 1.2 1 files changed, 1 insertions(+), 1 deletions(-)
Après avoir créé une nouvelle branche et switché dedans on
applique le numéro de version. Ici, bump-version.sh
est un
script shell imaginaire qui change quelques fichiers pour indiquer
la nouvelle version. Cela peut bien sûr être fait à la main. Puis
l'ajout du numéro de version est commité.
Cette nouvelle branche peut rester là un certain temps avant que la release ne se produise définitivement en la mergeant dans la branche master. Pendant ce temps, des bug fixes peuvent être appliqués à cette branche au lieu de la branche develop.
L'ajout de grandes nouvelles fonctionnalités est strictement interdit. Celles-ci doivent être mergées dans la branche develop et attendre la prochaine grande release.
Fin d'une branche de release
Quand l'état de la branche release est prêt à devenir une vraie release, certaines actions sont à effectuer.
- La branch release est mergée dans master (chaque commit dans master est une nouvelle release par définition)
- Ce commit doit être tagé pour faciliter sa référence dans l'historique du projet.
- Enfin, les changements fait sur la branch release doivent être mergés en retour dans la branch develop pour que les futurs releases comportent les bug fixes qui ont été apportés.
$ git checkout master Switched to branch 'master' $ git merge --no-ff release-1.2 Merge made by recursive. (Summary of changes) $ git tag -a 1.2
Pour garder les changements apportés à la release branch, on doit les merger dans la branch develop.
$ git checkout develop Switched to branch 'develop' $ git merge --no-ff release-1.2 Merge made by recursive. (Summary of changes)
Cette étape peut donner lieu à un conflit de merge (c'est plus que probable puisque le numéro de version a été changé)
Maintenant que les choses sont faites, la release branch peut être supprimé. Nous n'en avons plus besoin.
$ git branch -d release-1.2 Deleted branch release-1.2 (was ff452fe).
Hotfix branches (branches de dépannages à chaud)
- Démarrent depuis : master
- Retournent à : develop ET master
- Nommées : hotfix-\*
Les branches de dépannage sont semblables aux branches de release car elles visent à préparer une nouvelle release de production. La différence c'est qu'elles ne sont pas planifiées.
Elles naissent de la nécessité d'agir rapidement pour corriger un état indésirable de fonctionnement en production.
Quand un bug critique dans la version de production doit être résolu immédiatement, une branche hotfix est créée à partir d'un tag de la branche master qui représente la version en production.
Création de la branche hotfix
Les branches hotfix sont créées à partir de la branche master. Admettons que la version 1.2 est la version de production live en cours et qu'elle connaît un bug sévère.
La branche develop de son côté est encore instable.
$ git checkout -b hotfix-1.2.1 master Switched to a new branch "hotfix-1.2.1" $ ./bump-version.sh 1.2.1 Files modified successfully, version bumped to 1.2.1. $ git commit -a -m "Bumped version number to 1.2.1" [hotfix-1.2.1 41e61bb] Bumped version number to 1.2.1 1 files changed, 1 insertions(+), 1 deletions(-)
Le numéro de version est modifié dès la création de la branche. La réparation peut se faire ensuite en un ou plusieurs commits.
$ git commit -m "Fixed severe production problem" [hotfix-1.2.1 abbe5d6] Fixed severe production problem 5 files changed, 32 insertions(+), 17 deletions(-)
Clôture de la branche hotfix
Une fois terminée, la correction du bug doit être mergée dans master mais également dans develop de façon à ce que cette correction soit également incluse dans la prochaine release.
$ git checkout master Switched to branch 'master' $ git merge --no-ff hotfix-1.2.1 Merge made by recursive. (Summary of changes) $ git tag -a 1.2.1
Ensuite, on ajoute le bugfix à la branche develop.
$ git checkout develop Switched to branch 'develop' $ git merge --no-ff hotfix-1.2.1 Merge made by recursive. (Summary of changes)
La seule exception à ce schéma, c'est lorsqu'une branche release est en cours. Les changements de la branche hotfix doivent être mergés dans la branche release au lieu de la branche develop.
Le merge du hotfix dans la branche release va de toute manière être inclus dans la branche develop quand la branche release est achevée.
(Si la branche develop requière immédiatement cette correction de bug et ne peut attendre la fin de la release branch, on peut également y merger le bugfix)
Enfin, cette branche temporaire est supprimée.
$ git branch -d hotfix-1.2.1 Deleted branch hotfix-1.2.1 (was abbe5d6).
Conclusions
Les branches renseignent sur les états d'avancement et de stabilité d'un projet. Il suffit de constater la présence d'une branche hotfix pour saisir que le projet connait une situation critique puisque l'intervention concerne un code déployé en production.
Les branches release sont également un moment fort dans la vie d'un projet car elles renseignent sur la prochaine mutation du code en production et valident les travaux entrepris jusque-là sur les branches de fonctionnalité.
Au-delà de la facilité et des optimisations introduites par Git dans la gestion du code source, ce système est un indicateur de la vitalité des projets qui l'utilisent.