• 15/04/2021
  • Jules Chevalier
  • GIT

L’une des forces de Git est de nous permettre de créer, modifier et voyager entre différentes versions parallèles d’un projet, tel le Quinn Mallory de l’IT (si vous n’avez pas cette référence, honte à vous). Afin d’éviter de nous perdre dans le cosmos, voyons comment manipuler le continuum spacio-temporel pour voyager à travers différentes versions d’un projet.

Après le dernier Git Peaks #2 consacré aux commits, et particulièrement sur la manière de créer un historique propre et compréhensible, prenons un peu de hauteur en nous attardant sur le cas des branches !

Pourquoi les branches

Les branches dans Git permettent de faire évoluer plusieurs versions d’un projet en parallèle, sans que l’un n’ait d’incidence sur les autres. Il est ensuite possible de fusionner différentes versions, de mettre à jour une version par rapport à une autre, ou bien de laisser tomber une version, définitivement ou non (le fameux effet bac à sable de Git).

À titre d’exemple, la branche master représente souvent la version stable du projet, qui sera distribuée aux utilisateurs. Ensuite, plusieurs stratégies existent, où de nouvelles branches vont permettre de travailler sur une nouvelle fonctionnalité, corriger un bug ou perfectionner la version beta, sans pour autant toucher à la version stable du projet. Ainsi, master reste stable et disponible à tout moment. Lorsque le travail sur une branche parallèle est terminé, testé et approuvé, il peut être fusionné avec la branche master. La version stable du projet contient désormais les nouveaux ajouts développés dans la branche parallèle.

Manipuler les branches

Quelques commandes Git permettent de manipuler les branches et de se déplacer à travers les différentes versions du projet. Les actions principales sont de créer une branche et de changer de branche courante. Mais tout d’abord, un peu de repérage.

Se repérer dans le multivers

Nous ne présentons plus git status qui permet d’avoir un état général du repository, et justement en premier lieu la branche courante.

$ git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

Pour avoir une vision plus précise des branches existantes, git branch permet de les lister, accompagnées des branches “distances” (présentes sur le serveur) grâce à l’option -a. La branche courante est toujours précédée d’une étoile.

$ git branch
* master

$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/cpp-hello-world
  remotes/origin/master

Créer une branche et se déplacer

Pour créer une nouvelle branche, on utilise de nouveau branch suivie du nom que l’on souhaite donner. La nouvelle branche sera à sa création identique à la branche courante.

$ git branch new-feature

$ git branch
* master
  new-feature

$ git log
480b8fd  (HEAD -> master, origin/master, origin/HEAD, new-feature) Merge branch 'python-hello-world'
Wed Oct 14 15:27:22 2020 +0200 Jules Chevalier 

On remarque que l’on est toujours sur la branche master. Pour changer de branche et passer sur la nouvelle branche, on va utiliser git checkout.

$ git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

$ git checkout new-feature 
Switched to branch 'new-feature'

$ git status
On branch new-feature
nothing to commit, working tree clean

Pour effectuer la création d’une branche et la bascule vers la nouvelle branche en une seule commande, on peut utiliser l’option -b de Git.

$ git checkout -b new-feature
Switched to a new branch 'new-feature'

La nouvelle branche va donc évoluer indépendamment de master et les nouvelles modifications ne seront apportées qu’à cette branche. Grâce à ce système, on peut donc changer de version à la volée, en mettant le travail en cours en attente, puis revenir plus tard à cette version sans rien perdre, et sans manipulation complexe.

Pour illustrer tout cela, un petit exemple de travail sur deux branches indépendantes.

$ git status
On branch new-feature
nothing to commit, working tree clean

# Ajout d'un nouveau fichier et commit sur new-feature
$ touch new-feature.txt

$ git status
On branch new-feature
Untracked files:
  (use "git add ..." to include in what will be committed)
	new-feature.txt

nothing added to commit but untracked files present (use "git add" to track)

$ git add new-feature.txt 

$ git commit -m'chore: add test file'
[new-feature 23a538e] chore: add test file
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 new-feature.txt

# Le nouveau commit est bien présent
$ git log
23a538e  (HEAD -> new-feature) chore: add test file
Tue Feb 2 16:10:55 2021 +0100 Jules Chevalier 

480b8fd  (origin/master, origin/HEAD, new-feature, master) Merge branch 'python-hello-world'
Wed Oct 14 15:27:22 2020 +0200 Jules Chevalier 

# Mais n'apparaît pas sur la branche master, qui est restée intacte
$ git checkout master 
Switched to branch 'master'

$ git log
480b8fd  (HEAD -> master, origin/master, origin/HEAD, new-feature) Merge branch 'python-hello-world'
Wed Oct 14 15:27:22 2020 +0200 Jules Chevalier 

Pousser sa nouvelle branche

Comme toujours, l’objectif premier de Git est de sauvegarder et partager son travail. Pour cela, pousser une branche sur un serveur Git (Gitlab, Github…) permet de sécuriser les modifications avant toute opération supplémentaire. Une fois de plus c’est vers git push que l’on se tourne, à quelques détails près.

Si l’on se contente de git push notre nouvelle branche, git nous averti qu’il ne connaît pas la destination de la branche, et est donc dans l’incapacité de terminer l’opération.

Pourquoi est-ce que la question ne s’est pas posée précédemment avec master ? Parce que la branche master a été récupérée directement du serveur, lors du git clone, et a été associée à son pendant serveur, que l’on nomme origin/master, appelée upstream branch.

$ git checkout new-feature 
Switched to branch 'new-feature'
$ git push fatal: The current branch new-feature has no upstream branch. To push the current branch and set the remote as upstream, use git push --set-upstream origin new-feature

Pour palier cette situation, il faut indiquer directement à Git le nom de la branche à pousser sur le serveur avec git push origin new-feature. On peut, avec l’option -u indiquer à Git que l’on veut désormais lier la branche locale à son pendant distant, évitant de refaire cette précision à chaque push.

$ git push -u origin new-feature 
[...]
To git.peaks.fr:jchevalier/git-example.git
 * [new branch]      new-feature -> new-feature
Branch 'new-feature' set up to track remote branch 'new-feature' from 'origin'.

Maintenant que la nouvelle branche est sauvegardée, voyons comment rapatrier ses modifications dans la branche principale master.

Fusiooooooon /o/\o\

De façon évidente, l’utilisation de branches distinctes prend tout son sens par la fusion de celles-ci. Lorsque le travail sur une branche est terminé, il est temps de la fusionner avec une branche principale, afin de rapatrier le travail fait “en parallèle” dans cette nouvelle branche. Cette fusion se fait en deux temps : bascule vers la branche cible, et fusion des modifications de la source vers la cible. La bascule se fait toujours avec git checkout, et la fusion se fait avec git merge suivi du nom de la branche source.

$ git checkout master 
Switched to branch 'master'
Your branch is up to date with 'origin/master'.

$ git merge new-feature 
Updating 480b8fd..23a538e
Fast-forward
 new-feature.txt | 0
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 new-feature.txt
 
$ git log
23a538e  (HEAD -> master, origin/new-feature, new-feature) chore: add test file
Tue Feb 2 16:10:55 2021 +0100 Jules Chevalier 

480b8fd  (origin/new-feature, origin/master, origin/HEAD, new-feature) Merge branch 'python-hello-world'
Wed Oct 14 15:27:22 2020 +0200 Jules Chevalier 

On peut voir suite à la fusion des branches que master a bien récupéré le commit créé dans new-feature.

Conclusion

Les branches donnent clairement toute sa puissance à Git. Grâce à elles, il est possible de travailler en parallèle sur plusieurs versions du projet, sans interférences, tout en bénéficiant de la sauvegarde et du partage du travail effectué. Une version du projet reste stable en toute circonstance, pendant que les nouvelles fonctionnalités et les bugs sont traités dans des versions parallèles simultanément.

Il y a bien évidemment beaucoup à ajouter pour balayer l’étendue des possibilités mais aussi des difficultés apportées par l’utilisation des branches. Un article entier serait nécessaire pour parler de la gestion des conflits, lorsque l’on souhaite fusionner des branches dont les modifications ne sont pas compatibles, ou encore sur les différents modèles d’utilisation des branches, fameux Workflow Git donc GitFlow est le plus connu…