Git en entreprise
Mise à jour du 1er décembre 2013 : Sinon git flow c'est pas mal aussi ;)
Je suis actuellement en poste dans deux entreprises :
- Lead développeur chez JolieBox, où je passe mes connaissances car mon départ est prévu pour le 1er octobre 2012.
- Lead développeur chez Internim où j'arrive petit à petit pour prendre en main tout ou partie du Lead sur certains projets et pour animer un peu les développeurs.
J'ai une problématique qui se présente à la fois chez JolieBox et à la fois chez Internim : Notre workflow git.
L'idée de cet article est de permettre aux développeurs de savoir quelles commandes utiliser, et dans quels cas !
Rien de mieux qu'un bon exemple. Vous allez avoir du concret !
Les branches
Avant mon arrivée chez Internim nous avons décidé, chez JolieBox, de travailler avec une branche par développeur.
Chaque développeur a donc une branche qui lui est propre, nommée le plus souvent par son pseudo ou son nom : jbh
pour moi !
A ce moment là nous avons établi le fonctionnement :
- Une branche par développeur.
- Une branche
master
. - Une branche
dev
si besoin. - Une branche supplémentaire par gros lot de développements.
A mon arrivée chez Internim j'ai découvert que le fonctionnement était le même sur les projets sous Git, avec quelques branches en plus :
- Une branche
integration
qui contient donc l'intégration du projet (agence web oblige). - Une branche
recette
qui contient la dernière release du projet. - Une branche
demo
qui permet surtout d'avoir une environnement adapté.
Nous sommes donc sur un schéma assez complexe car nous avons un minimum de 3 branches chez JolieBox et 6 branches chez Internim.
Si à cela on ajoute au moins 3 ou 4 branches pour les développeurs, ça devient rapidement un beau bordel. Et nous ne sommes pas dans des process de releases comme Debian ou autre.
Les développements
J'ai lu un très bon article qui résume bien le fonctionnement que je vais vous expliquer ici.
git commit
On ne vous la présente pas ! Comment faire nos commits sans cette ligne de commande ?
La meilleure.
git checkout
La commande git checkout
permet donc de changer de branche.
Elle est utilisée dans le cas où on doit se placer dans l'état d'une autre branche (la master
pour un hotfix par exemple).
Elle est aussi utilisée avant de faire un git merge
.
git merge
La commande git merge
permet de merger une branche sur la branche courante.
Imaginez que vous êtes sur la branche jbh
et que vous devez merger cette branche jbh
sur la master
:
$ git checkout master
$ git merge jbh
Rien de plus.
L'option --no-ff
permet de ne pas faire de fast-forward et de forcer la création d'un commit.
git cherry-pick
J'adore cette commande. Elle permet de récupérer un commit...
(RTFM)
git-cherry-pick - Apply the changes introduced by some existing commits
Je l'utilise quand je dois appliquer un hotfix sur ma branche par exemple. Plutôt qu'un merge...
Par l'exemple
Rien ne vaut un exemple pour bien comprendre tout ça.
Prenons les branche suivantes :
- master
- dev
- recette
- demo
- dev1 (développeur 1)
- dev2 (développeur 2)
- dev3 (développeur 3)
Et établissons l'historique suivant : (Espace de travail du Développeur 2)
* Added f (origin/dev3)
* Added e
| * Added d (HEAD, origin/dev2, dev2)
| * Added c
|/
| * Added b (origin/dev1)
|/
* Added a (origin/recette, origin/master, origin/dev, origin/demo, origin/HEAD, master)
On peut voir que les développeurs on avancé chacun de leur côté au départ de la master.
La branche dev
Le Développeur 2 veut merger sa branche sur la dev
car il a terminé ses développements :
$ git checkout dev
$ git merge --no-ff dev2
$ git push origin dev
Et voici ce que ça donne :
* Merge branch 'dev2' into dev (HEAD, origin/dev, dev)
|\
| * Added d (origin/dev2, dev2)
| * Added c
|/
| * Added f (origin/dev3)
| * Added e
|/
| * Added b (origin/dev1)
|/
* Added a (origin/recette, origin/master, origin/demo, origin/HEAD, master)
Il se trouve que le Développeur 2 se trouve sur la dernière révision de la dev
, il peut donc reprendre ses développements.
$ git checkout dev2
[quelques commits...]
$ git push origin dev2
On remarque que le merge de dev2
sur dev
n'a impacté que la branche dev
.
Il faut savoir que le merge n'impacte QUE la branche courante.
* Added h (HEAD, origin/dev2, dev2)
* Added g
| * Merge branch 'dev2' into dev (origin/dev, dev)
| |\
| |/
|/|
* | Added d
* | Added c
|/
| * Added f (origin/dev3)
| * Added e
|/
| * Added b (origin/dev1)
|/
* Added a (origin/recette, origin/master, origin/demo, origin/HEAD, master)
Maintenant c'est au tour du Développeur 1 de vouloir merger sur la dev
.
$ git checkout dev
$ git fetch
$ git merge origin/dev
$ git merge --no-ff dev1
$ git push origin dev
Le résultat : La branche dev
est à jour avec les commits des développeurs 1 et 2. Cependant le Développeur 1 n'a pas dans sa branche dev1
les commits de la branche dev2
et le Développeur 2 n'a pas ceux de la branche dev1
dans sa branche dev2
.
Le Développeur 1 sait que sa branche n'est pas totalement à jour. Il décide donc de faire en sorte qu'elle le soit pour pouvoir continuer ses développements.
Pour ça il décide de replacer sa branche dev1
à l'identique de la branche dev
. On reset :
$ git checkout dev1
$ git reset --hard dev
$ git push -f origin dev1
D'après un collègue chez Internim on peut aussi faire un pull/push de la branche sur laquelle on veut se placer : (testé et approuvé)
$ git checkout dev1
$ git pull origin dev
$ git push origin dev1
Le Développeur 1 est maintenant précisément au même endroit que la branche dev
, il peut donc continuer ses développements avec deux nouveaux commits :
* Added j (HEAD, origin/dev1, dev1)
* Added i
* Merge branch 'dev1' into dev (origin/dev, dev)
|\
| * Added b
* | Merge branch 'dev2' into dev
|\ \
| |/
|/|
| | * Added h (origin/dev2)
| | * Added g
| |/
| * Added d
| * Added c
|/
| * Added f (origin/dev3)
| * Added e
|/
* Added a (origin/recette, origin/master, origin/demo, origin/HEAD, master)
Vient le tour du Développeur 3 de faire son merge sur la branche dev
(rapidement) :
$ git checkout dev
$ git fetch
$ git merge origin/dev
$ git merge --no-ff dev3
$ git push origin dev
$ git checkout dev3
On remarque que la branche dev
ne contient QUE des merges. C'est super car c'est exactement ce que nous voulons !
* Merge branch 'dev3' into dev (origin/dev, dev)
|\
| * Added f (HEAD, origin/dev3, dev3)
| * Added e
| | * Added j (origin/dev1)
| | * Added i
| |/
|/|
* | Merge branch 'dev1' into dev
|\ \
| * | Added b
| |/
* | Merge branch 'dev2' into dev
|\ \
| |/
|/|
| | * Added h (origin/dev2)
| | * Added g
| |/
| * Added d
| * Added c
|/
* Added a (origin/recette, origin/master, origin/demo, origin/HEAD, master)
Au tour du Développeur 3 de replacer sa branche pour continuer ses développements correctement :
$ git checkout dev3
$ git reset --hard dev
$ git push -f origin dev3
On peut aussi faire un pull de la branche dev
:
$ git checkout dev3
$ git pull origin dev
$ git push origin dev3
Bien sûr un reset --hard
suivi d'un push -f
implique que votre environnement de travail soit vraiment propre avant toute manipulation.
La branche recette
On ne merge sur la recette
que quand les développements sont validés. C'est à ce moment là que le chef de projets fait ses vérifications par rapport aux spécifications techniques générales et détaillées.
N'importe qui peut faire le merge de la dev
sur la recette
car on sait que seule la branche courante est impactée.
On merge la branche origin/dev
pour être certain de prendre la dernière version :
$ git checkout recette
$ git fetch
$ git merge origin/recette
$ git merge --no-ff origin/dev
$ git push origin recette
La branche recette
contient bien nos derniers développements.
* Merge remote-tracking branch 'origin/dev' into recette (HEAD, recette)
|\
| * Merge branch 'dev3' into dev (origin/dev3, origin/dev, dev3, dev)
| |\
| | * Added f
| | * Added e
| |/
|/|
| | * Added j (origin/dev1)
| | * Added i
| |/
| * Merge branch 'dev1' into dev
| |\
| | * Added b
| |/
|/|
| * Merge branch 'dev2' into dev
| |\
|/ /
| | * Added h (origin/dev2)
| | * Added g
| |/
| * Added d
| * Added c
|/
* Added a (origin/recette, origin/master, origin/demo, origin/HEAD, master)
La branche demo
Le principe est identique à la branche recette
sauf que c'est bien la branche recette
que l'on merge sur la demo
.
La branche master
C'est un peu la branche de prod...
Même principe, on merge la branche demo
sur la master
.
Les hotfixes ?
C'est le dernier point important !
A un moment il est fort possible que vous ayez à faire un hotfix sur la branche recette
par exemple.
Faites-le ! Et appliquez la modification sur la branche dev
pour être certain que les développeurs récupèrerons la modification à un moment.
Pour ça vous pouvez procéder comme suit :
Appliquer le hotfix
$ git checkout recette
[...fix...]
$ git fetch
$ git merge origin/recette
$ git commit -m "[hotfix] Added k"
$ git push origin recette
Il nous faut maintenant appliquer le commit sur toutes les principales branches du projet au dessus dans la hiérarchie des merges.
Dans ce cas au dessus de recette
: c'est dev
.
Appliquer le hotfix sur la branche dev
Les branches dev1
, dev2
et dev3
ne sont pas à impacter. Si votre équipe est bien organisée elle verra passer le hotfix et le récupèrera.
Soit de la manière que je vais vous montrer, soit via un reset comme nous l'avons vu plus haut.
Appliquez donc le hotfix sur la branche dev
via la commande git cherry-pick
!
Il vous faut au préalable le SHA1 du hotfix (au moins les premiers caractères) :
$ git checkout dev
$ git fetch
$ git merge origin/dev
$ git cherry-pick 461c1ca
$ git push origin dev
Conclusion
Je ne suis pas certain que ce workflow soit le meilleur mais il a le mérite de fonctionner. Il permet de garder une arborescence propre.
Le fait de reset --hard
la branche du développeur est assez particulier, mais c'est la seule manière que j'ai trouvé pour ne pas faire de duplicats de commits (comme on peut le voir avec rebase
)
C'est essentiellement à cause de ces reset
que je force le commit lors des merges avec l'option --no-ff
. De cette manière mon historique indique bien qu'il y a eu un merge de tel développeur à tel moment.
Vos avis sont les bienvenus !