Git & GitLab

Maxence Lagalle

Avril 2023

Licence Creative Commons
Cette formation est mise à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 4.0 International.

Introduction

Pourquoi utiliser un système de gestion de versions ?

Système de gestion de versions ?

  • Un système de gestion de versions (VCS) est un outil permettant de suivre les modifications apportées au code source d’un projet au fil du temps.

  • Son utilisation permet de :

    • travailler ensemble sur un même code source
    • conserver l’historique des modifications apportées
    • revenir à des versions précédentes
    • résoudre les conflits entre les modifications apportées par différents développeurs

Quels sont les avantages ?

Sécurité

  • Sauvegarde automatique : les développeurs n’ont pas besoin de se rappeler de faire des sauvegardes régulières de leur code, car le VCS s’en charge automatiquement.
  • Facilité de récupération : si un développeur supprime accidentellement un fichier important ou introduit une erreur dans le code, il peut facilement revenir à une version précédente du code grâce à l’historique des modifications stocké dans le VCS.

Travail en équipe

  • Collaboration efficace : chaque développeur peut travailler sur sa propre branche de code et fusionner ses modifications avec la branche principale une fois qu’il est satisfait de son travail.
  • Gestion des conflits : si deux développeurs ont modifié le même fichier de code, le VCS les alertera et les aidera à résoudre le conflit en fusionnant les modifications de manière appropriée.

Documentation

  • Historique des modifications : les développeurs peuvent facilement accéder à des versions antérieures du code et voir qui a effectué quelles modifications, quand et pourquoi.
  • Commentaires : lorsqu’un développeur effectue des modifications avec un VCS, il peut ajouter un message pour décrire les modifications apportées au code.

Git ?

  • Git a été créé par Linus Torvalds, le créateur du système d’exploitation Linux, en 2005.
  • Il a été conçu pour être un VCS décentralisé, capable de gérer efficacement les grands projets open source tels que le développement du noyau Linux.
  • Git a rapidement gagné en popularité auprès de la communauté du développement de logiciels en raison de sa rapidité, de sa facilité d’utilisation et de sa flexibilité.

Système décentralisé ?

  • Git, en tant que VCS décentralisé (ou distribué), permet à chaque développeur de disposer d’une copie complète de l’historique des modifications et du code source sur leur ordinateur.
  • Ils peuvent dont travailler en mode déconnecté (sans accès à Internet ou à un serveur centralisé) et ont accès à toutes les versions du code source.
  • Cela signifie qu’il n’y a pas de point unique de défaillance, car le référentiel de code est distribué sur plusieurs machines.

GitLab ?

  • GitLab est une plateforme web open source de gestion de projets de développement de logiciels qui utilise Git pour la gestion de versions de code source.
  • GitLab fournit des fonctionnalités de collaboration supplémentaires telles que des tableaux de bord de suivi de projets, des outils de gestion de bugs ou bien encore des outils d’intégration continue.
  • Il fournit également des fonctionnalités de gestion d’équipe telles que la gestion des utilisateurs, des permissions et des groupes de projets.

Premiers pas

Installation et configuration de Git

Installation de Git

Linux

  • Git est généralement installé directement dans la plupart des distributions Linux.
  • Sinon, il est installable dans un terminal au travers du gestionnaire de paquets de la distribution :
    • Debian : sudo apt install git-all
    • Fedora : sudo dnf install git-all

macOS

  • Sur macOS Mavericks (10.9) ou ultérieur, Git est fourni au travers des Xcode Command Line Tools.
  • Il suffit d’essayer de lancer Git dans un terminal via une commande comme git --version pour que macOS propose de l’installer avec d’autres outils de développement.
  • Sinon, il est possible d’utiliser Homebrew via brew install git ou de télécharger l’installateur.

Windows

  • Sur Windows, l’installation de Git est possible grâce au projet Git for Windows qui fournit Git avec un émulateur de ligne de commande Bash.
  • Une façon simple de l’installer de manière automatisée est d’utiliser Scoop pour installer Git avec la commande scoop install git.
  • Sinon, il est possible de télécharger l’installateur via le site officiel ou avec WinGet.

Choix de l’éditeur de texte par défaut

Choosing the default editor used by Git
  • Vim est un bon choix pour des utilisateurs habitués à utiliser des programmes en ligne de commande.
  • Sinon, il vaut mieux utiliser un éditeur avec une interface graphique comme Notepad++ ou VS Code.

Nom de la branche initiale dans les nouveaux dépôts

Adjusting the name of the initial branch in new repositories
  • Il est désormais recommandé pour travailler avec des outils tiers de sélectionner l’option Override the default branch name for new repositories et de choisir main comme nom par défaut.

Inscription de Git dans les variables d’environnement

Adjusting your PATH environment
  • Git from the command line and also from 3rd-party software permet d’utiliser Git avec d’autres logiciels installés sur l’ordinateur, notamment des IDE.
  • Use Git and optional Unix tools from the Command Prompt donne accès à des commandes Unix dans la ligne de commande Windows.

Choix de l’exécutable SSH

Choosing the SSH executable
  • Depuis Windows 10, un client SSH est directement intégré à Windows. Il est donc possible de l’utiliser en choisissant Use external OpenSSH pour mutualiser les configurations.

Choix du magasin de certificats HTTPS

Choosing HTTPS transport backend
  • L’option Use the native Windows Secure Channel library permet de bénéficier des certificats de sécurité déployés dans un cadre professionnel via un domaine Active Directory.

Conversion des fins de ligne

Configuring the line ending conventions
  • À configurer en fonction des habitudes de l’équipe :
    • Checkout Windows-style, commit Unix-style line endings est recommandé sous Windows pour un projet multi-plateformes.
    • Checkout as-is, commit Unix-style line endings peut s’utiliser si toute l’équipe travaille avec des logiciels adaptés au monde Unix.

Comportement par défaut du pull

Choose the default behavior of git pull
  • À configurer en fonction des habitudes de l’équipe.
    • En l’absence de préférence, l’option Default (fast-forward or merge) est plus simple à appréhender dans un premier temps.

Configuration de Git

Niveaux de configuration

    flowchart TD
      subgraph system
        subgraph global
          subgraph local
            node[Configuration]
          end
        end
      end
  
  • De manière générale, la configuration se définit à l’échelle de l’utilisateur, donc au niveau global.
  • Le niveau local peut également être utilisé pour des paramètres spécifiques à un projet.

Configuration minimale

  • La configuration de Git s’effectue en ligne de commandes (CLI) dans un terminal.
  • Il faut au minimum configurer un nom d’auteur et une adresse e-mail :
git config --global user.name "Prénom Nom"
git config --global user.email "utilisateur@domaine.tld"
  • Dans un cadre professionnel, la configuration d’un proxy peut également être nécessaire :
 git config --global http.proxy "http://proxy.domaine.tld:port"
 git config --global https.proxy "http://proxy.domaine.tld:port"
 git config --global no.proxy "exception1,exception2,localhost"

Consulter la configuration active

  • La commande git config permet également de connaître la valeur actuelle d’un paramètre.
  • Il est même possible de consulter toute la configuration actuellement active grâce à la commande suivante :
    git config --list
    
    • L’option --show-origin permet d’indiquer en plus si une configuration vient du niveau system, global ou local.

Obtenir de l’aide

  • Git propose une aide intégrée (en anglais) sur son fonctionnement et la syntaxe de ses commandes. Cette aide est accessible via la commande :
    git help <commande>
    
    • En cas d’utilisation de git help sans préciser de nom de commande, Git écrira dans le terminal une liste des commandes les plus usuelles.

Gérer un dépôt Git

Créer un dépôt, écrire des commits, gérer des multiples branches, consulter et manipuler l’historique

Qu’est-ce qu’un dépôt ?

  • Un dépôt (ou “repository” en anglais) est l’endroit où Git stocke l’historique des versions d’un projet de développement.
  • Il existe deux types de dépôts dans Git :
    • Un dépôt local est une copie du dépôt sur l’ordinateur local d’un développeur
    • Un dépôt distant est un dépôt hébergé sur un serveur distant, accessible via Internet.

Créer un dépôt local

  • Pour créer un dépôt local avec Git, il suffit de se positionner en ligne de commandes (via cd) dans un dossier à transformer en dépôt et d’utiliser la commande :
    git init
    
    • Ceci va créer un sous-dossier .git (caché sous UNIX, pour le voir : ls -la) qui sert notamment à stocker l’historique.
      • Il n’est pas utile d’y accéder en utilisation courante.
  • Il est possible de consulter l’état d’un dépôt grâce à la commande :
    git status
    
    • Pour un dépôt nouvellement créé dans un dossier vide, elle doit proposer la sortie suivante :
      On branch main
      
      No commits yet
      
      nothing to commit (create/copy files and use "git add" to track)
      

Qu’est-ce qu’un commit ?

  • Un commit représente une version spécifique d’un projet à un moment donné. C’est l’unité de base de l’historique d’un dépôt.
    • Il contient une copie des fichiers du projet, ainsi qu’un message décrivant les modifications apportées depuis la version précédente.
  • Un commit est identifié par un hash (SHA-1) calculé par Git sur la base du hash de l’ensemble des fichiers du projet.
    • On l’abrège souvent à ses premiers caractères (par exemple, 1d0703d3).

Manipulation des fichiers

  • Avec Git, un fichier peut être présent dans 3 zones distinctes :
    • le répertoire de travail
    • l’index
    • le dépôt
  • git status permet de connaître l’état de chaque fichier

Répertoire de travail

  • Le répertoire de travail correspond au contenu du dossier sur l’ordinateur du développeur.
    • Par défaut, aucun fichier n’est suivi par Git. Par exemple, après la création d’un fichier exemple.txt, la commande git status donne la sortie suivante :
      On branch main
      
        No commits yet
      
        Untracked files:
        (use "git add <file>..." to include in what will be committed)
        exemple.txt
      
        nothing added to commit but untracked files present (use "git add" to track)
      
    %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true }}}%%
    classDiagram
      direction RL
      Index..Répertoire
      Dépôt..Index
      class Répertoire{
        +exemple.txt a4d584
      }
      class Index{
      }
      class Dépôt{
      }
  

Index

  • Pour demander à Git de commencer à suivre un fichier, il faut l’ajouter à l’index.
  • L’index est une zone tampon entre le répertoire de travail et le dépôt qui rassemble l’ensemble des modifications prêtes à être ajoutées à un commit.
  • Concrètement, on indexe un fichier avec la commande suivante :
    git add <fichier>
    
    • On peut utiliser un . à la place du nom du fichier pour demander à Git d’indexer tout le répertoire de travail.
  • Une fois un fichier indexé, il est prêt à être inclus à un commit. La sortie de git status reflète cette évolution :
    On branch main
    
    No commits yet
    
    Changes to be committed:
    (use "git rm --cached <file>..." to unstage)
    new file:   exemple.txt
    
  • Il est possible de désindexer un fichier sans le supprimer avec la commande :
    git rm --cached <fichier>
    
    • L’option -f peut être utilisée à la place de --cached pour supprimer totalement le fichier.
  • Un fichier indexé devient également suivi par Git, ce qui revient à dire que son hash est recalculé à chaque modification du répertoire de travail.
  • S’il est à nouveau modifié alors qu’il est toujours dans l’index, Git détectera cette modification :
    On branch main
    
    No commits yet
    
    Changes to be committed:
    (use "git rm --cached <file>..." to unstage)
    new file:   exemple.txt
    
    Changes not staged for commit:
    (use "git add <file>..." to update what will be committed)
    (use "git restore <file>..." to discard changes in working directory)
    modified:   exemple.txt
    
    %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true }}}%%
    classDiagram
      direction RL
      Index<|--Répertoire : git add
      Dépôt..Index
      class Répertoire{
        +exemple.txt a4d584
      }
      class Index{
        +exemple.txt a4d584
      }
      class Dépôt{
      }
  

Dépôt

  • À partir du moment où un fichier est présent dans au moins un commit, il est alors enregistré dans le dépôt.
  • Un commit est composé de l’ensemble des fichiers du dernier commit de la branche actuelle (HEAD) auxquels sont appliquées les modifications de l’index.
  • Pour effectuer un commit, on utilise la commande suivante :
    git commit -m "Message de commit"
    
  • Une fois qu’un commit est réalisé, il devient le nouveau commit de référence (HEAD) à partir duquel Git vérifie si un fichier a été modifié.
  • Quand une modification a été incluse dans un commit, elle sort de l’index, comme en témoigne la sortie de git status :
    On branch main
    nothing to commit, working tree clean
    
    %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true }}}%%
    classDiagram
      direction RL
      Index..Répertoire
      Dépôt<|--Index : git commit
      class Répertoire{
        +exemple.txt a4d584
      }
      class Index{
      }
      class Dépôt{
        +exemple.txt a4d584
      }
  

Messages de commit

  • Le message de commit sera lisible par tous les développeurs du projet et sera retranscrit dans l’historique, d’où l’importance qu’il retrace fidèlement le contenu du commit.
    • Il est donc crucial que les modifications apportées par un commit forment un ensemble cohérent.
  • En utilisant la syntaxe git commit -m, la longueur du message est limitée à 49 caractères.
    • Il vaut souvent mieux séparer un gros commit difficile à résumer en plusieurs petits commits.

Modifier le dernier commit

  • Il est possible de corriger un oubli sur le dernier commit ou de modifier son message avec l’option --amend :
    git commit --amend -m "Nouveau message de commit"
    
    • Pour ne pas modifier le message de commit, il faut ajouter l’option --no-edit à la place de -m.
  • Les modifications actuellement contenues dans l’index seront alors fusionnées avec celles du dernier commit, ce qui va créer un nouveau commit qui remplace l’ancien.

Autres manipulations sur les fichiers

  • Git propose des commandes permettant de retranscrire d’autres manipulations usuelles sur les fichiers :
    • Déplacer ou renommer un fichier :
      git mv <ancien_nom> <nouveau_nom>
      
    • Supprimer un fichier :
      git rm <fichier_a_supprimer>
      
  • Ces modifications seront ajoutées à l’index et devront être commitées pour être retranscrites dans le dépôt.

Ignorer des fichiers

  • Il est également possible de demander à Git d’ignorer de manière permanente des fichiers.
    • Pour cela, il faut créer à la racine du dépôt un fichier nommé .gitignore (sans extension), qui continent la liste des fichiers et dossiers à ignorer.
    • Le fichier .gitignore doit être commité pour être pris en compte.
  • Il existe des outils en ligne comme gitignore.io pour avoir des listes usuelles de fichiers à ignorer selon les types de projets.

Consulter l’historique d’un dépôt

    %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true }}}%%
    gitGraph
      commit id: "3b1d805"
      commit id: "6a2b687"
      commit id: "49eb5d0"
      commit id: "8f7308f"
      commit id: "30d524c" tag: "HEAD"
  
  • La commande git log permet d’obtenir l’historique complet des commits d’un dépôt, dans l’ordre chronologique inversé :
    git log
    
    commit 30d524c6603ed1982bdf82eb54d7215a00b22328 (HEAD -> main)
    Author: Maxence Lagalle <contact@maxence.lagalle.fr>
    Date:   Fri Apr 14 12:31:00 2023 +0200
    
        Suppression du fichier exemple.txt devenu inutile
    
    commit 8f7308fc1099a44fb25af05c3ee7c6a7ca17f111
    Author: Maxence Lagalle <contact@maxence.lagalle.fr>
    Date:   Fri Apr 14 12:30:31 2023 +0200
    
        Correction d'une coquille dans le poème
    
    commit 49eb5d06716e1f0668681b417a884b3d808bacda
    Author: Maxence Lagalle <contact@maxence.lagalle.fr>
    Date:   Fri Apr 14 12:30:02 2023 +0200
    
        Le poème est plutôt un haiku
    
    commit 6a2b687e994b4c8341adc6542c313cb860dad452
    Author: Maxence Lagalle <contact@maxence.lagalle.fr>
    Date:   Fri Apr 14 12:29:24 2023 +0200
    
        Ajout d'un poème sur l'informatique
    
    commit 3b1d805510f43af8e7b9da97d0bf44dca127a9d1
    Author: Maxence Lagalle <contact@maxence.lagalle.fr>
    Date:   Thu Apr 13 16:56:41 2023 +0200
    
        Exemple de commit avec un fichier unique
    
  • L’option --oneline permet d’obtenir un affichage compact, avec une seule ligne par commit :
    git log --oneline
    
    30d524c (HEAD -> main) Suppression du fichier exemple.txt devenu inutile
    8f7308f Correction d'une coquille dans le poème
    49eb5d0 Le poème est plutôt un haiku
    6a2b687 Ajout d'un poème sur l'informatique
    3b1d805 Exemple de commit avec un fichier unique
    
  • L’option --stat permet de connaître le nombre de modifications effectuées sur chaque fichier :
    git log --stat
    
    commit 30d524c6603ed1982bdf82eb54d7215a00b22328 (HEAD -> main)
    Author: Maxence Lagalle <contact@maxence.lagalle.fr>
    Date:   Fri Apr 14 12:31:00 2023 +0200
    
        Suppression du fichier exemple.txt devenu inutile
    
    exemple.txt | 0
    1 file changed, 0 insertions(+), 0 deletions(-)
    
    commit 8f7308fc1099a44fb25af05c3ee7c6a7ca17f111
    Author: Maxence Lagalle <contact@maxence.lagalle.fr>
    Date:   Fri Apr 14 12:30:31 2023 +0200
    
        Correction d'une coquille dans le poème
    
    haiku.txt | 2 +-
    1 file changed, 1 insertion(+), 1 deletion(-)
    
    commit 49eb5d06716e1f0668681b417a884b3d808bacda
    Author: Maxence Lagalle <contact@maxence.lagalle.fr>
    Date:   Fri Apr 14 12:30:02 2023 +0200
    
        Le poème est plutôt un haiku
    
    poeme.txt => haiku.txt | 0
    1 file changed, 0 insertions(+), 0 deletions(-)
    
    commit 6a2b687e994b4c8341adc6542c313cb860dad452
    Author: Maxence Lagalle <contact@maxence.lagalle.fr>
    Date:   Fri Apr 14 12:29:24 2023 +0200
    
        Ajout d'un poème sur l'informatique
    
    poeme.txt | 3 +++
    1 file changed, 3 insertions(+)
    
    commit 3b1d805510f43af8e7b9da97d0bf44dca127a9d1
    Author: Maxence Lagalle <contact@maxence.lagalle.fr>
    Date:   Thu Apr 13 16:56:41 2023 +0200
    
        Exemple de commit avec un fichier unique
    
    exemple.txt | 0
    1 file changed, 0 insertions(+), 0 deletions(-)
    
  • Il est possible de combiner les options --oneline et --stat.

Filtrer l’historique

  • git log supporte de nombreuses options permettant de filtrer l’historique selon plusieurs critères :
    • -n <nombre> : n derniers commits
    • --before="AAAA-MM-JJ HH:mm" : commits plus anciens qu’une date
    • --after="AAAA-MM-JJ HH:mm" : commits plus récents qu’une date
    • --author="Nom" : commits signés par un auteur
    • -- <fichier> : commits affectant un fichier

Rechercher à l’intérieur des commits

  • L’option -S permet de rechercher une chaîne de caractères et d’afficher la liste des commits contenant cette chaîne à l’intérieur des fichiers qu’ils modifient :
    git log -S "Chaîne à rechercher"
    
  • Il existe également l’option -G pour faire une recherche par expression régulière (regex).

Consulter l’historique détaillé d’un fichier

  • La commande git blame permet de connaître l’historique détaillé d’un fichier en précisant de quel commit viennent chacune de ses lignes dans leur rédaction actuelle :
    git blame <fichier>
    
    • La sortie de cette commande affiche chaque ligne du fichier dans le terminal, précédé de l’identifiant du commit ayant introduit ou modifié cette ligne, du nom de l’auteur et de la date de ce commit.
  • Exemple :
    git blame haiku.txt
    
    6a2b687e poeme.txt (Maxence Lagalle 2023-04-14 12:29:24 +0200 1) Un clic malencontreux
    8f7308fc haiku.txt (Maxence Lagalle 2023-04-14 12:30:31 +0200 2) Fichier important supprimé
    6a2b687e poeme.txt (Maxence Lagalle 2023-04-14 12:29:24 +0200 3) Sauvegarde ? Jamais fait.
    

Comparer deux commits

  • Grâce à git diff, il est possible d’effectuer des comparaisons entre deux états d’un fichier.
    • Si la commande est utilisée sans paramètres, la comparaison s’effectue entre le répertoire de travail et l’index.
    • Il est possible de donner en paramètres un identifiant de commit pour comparer ce commit au répertoire de travail, ou deux identifiants de commit pour les comparer.
  • Par exemple, il est possible de comparer le commit 49eb5d0 à l’état actuel du dépôt, c’est-à-dire au commit HEAD:
    git diff 49eb5d0 HEAD
    
    diff --git a/exemple.txt b/exemple.txt
    deleted file mode 100644
    index e69de29..0000000
    diff --git a/haiku.txt b/haiku.txt
    index b3c75e6..2ba1e15 100644
    --- a/haiku.txt
    +++ b/haiku.txt
    @@ -1,3 +1,3 @@
    Un clic malencontreux
    -Fichier portant supprimé
    +Fichier important supprimé
    Sauvegarde ? Jamais fait.
    \ No newline at end of file
    
  • git diff permet également de vérifier les modifications qui seront incluses dans le commit en cours de préparation en comparant l’index et le dernier commit :
    git diff --staged
    

Rechercher un commit défectueux

  • L’outil git bisect permet de retrouver un commit défectueux (par exemple, ayant introduit un bug) par recherche dichotomique.
  • Il faut tout d’abord l’initialiser en lui précisant notamment l’identifiant d’un “mauvais commit” (ayant le problème) et d’un “bon commit” (n’ayant pas le problème) :
    git bisect start
    git bisect bad <mauvais commit, vide pour commit actuel>
    git bisect good <bon commit>
    
  • Git va alors sélectionner un commit situé entre ces deux bornes et remettre le répertoire de travail à l’état de ce commit :
    Bisecting: X revisions left to test after this (roughly Y steps)
    
  • Après avoir vérifié manuellement si le bug est toujours présent, il faut indiquer à Git le résultat de cette vérification avec l’une de ces commandes :
    • git bisect good si le bug est absent
    • git bisect bad si le bug est présent
  • Lorsque Git a réussi à repérer le commit ayant introduit le bug, il indique son identifiant dans la sortie du terminal, ainsi que les détails du commit :
    8f7308fc1099a44fb25af05c3ee7c6a7ca17f111 is the first bad commit
    commit 8f7308fc1099a44fb25af05c3ee7c6a7ca17f111
    ...
    
  • Pour sortir du mode de recherche et revenir à l’état initial du répertoire de travail, il faut réinitialiser git bisect via :
    git bisect reset
    

Annuler un commit

  • Git propose un mécanisme de revert permettant d’annuler les modifications d’un commit. Il consiste à écrire un nouveau commit qui continent l’inverse des modifications du commit original. Ce mécanisme s’utilise avec la commande suivante :
    git revert <commit_a_annuler>
    
    • Ce nouveau commit s’inscrit dans l’historique au moment où il est créé, comme n’importe quel autre commit.

Revenir à un état antérieur de l’historique

  • git reset permet de ramener un dépôt à un état antérieur en supprimant tous les commits réalisés après un commit donné :
    git reset <dernier_commit_a_conserver>
    
    • Avec --soft, le répertoire de travail est remis à l’état du commit ciblé et les modifications réalisées depuis sont remises dans l’index.
    • Sans option, les modifications restent dans le répertoire de travail, mais pas dans l’index.
    • --hard réinitialise définitivement le répertoire de travail et l’index.

Supprimer totalement un fichier de l’historique

  • Dans le cas où un fichier a été commité par erreur, il est possible de demander à Git de réécrire son historique en le filtrant pour supprimer totalement ce fichier :
    git filter-branch --index-filter \
    'git rm -rf --cached --ignore-unmatch <chemin_vers_le_fichier>' HEAD
    
  • Sur un système avec Python>=3.5 installé, l’extension git filter-repo fournit une commande simplifiée :
    git filter-repo --invert-paths --path <chemin_vers_le_fichier>
    

Tags

  • Les tags sont un moyen simple d’associer une étiquette (généralement, un numéro de version) à un commit.
  • Une fois qu’ils sont positionnés, les tags peuvent être utilisés à la place des identifiants de commits dans presque toutes les commandes de Git.

Ajouter un tag

  • Ajouter un tag s’effectue avec la commande :
    git tag <nom_du_tag> <commit>
    
    • Si aucun identifiant de commit n’est précisé, le tag s’appliquera au dernier commit (HEAD).
    • Il est possible d’ajouter des informations détaillées à un tag (par exemple, un changelog) en ajoutant l’option -a.

Manipuler les tags

  • Git peut fournir la liste des tags d’un dépôt :
    git tag --list
    
  • Il est également possible d’afficher les détails d’un tag, notamment sa date et son auteur :
    git tag show <nom_du_tag>
    
  • Enfin, il est possible de supprimer un tag :
    git tag -d <nom_du_tag>
    

Utiliser plusieurs branches

  • Avec celle de commit, la notion de branche et l’un des concepts clés de Git.
    • La maîtriser permet de considérablement simplifier le travail collaboratif.
  • Une branche est un enchaînement de commits parallèle à la version principale du projet.
    • Par convention, la version principale est la branche main (anciennement master).
  • Techniquement, une branche est un tag dynamique qui se déplace automatiquement sur le dernier commit en date de sa lignée.
    • HEAD est le tag du dernier commit de la branche actuellement active.
    %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true }}}%%
    gitGraph
      commit
      commit
      branch feature
      commit
      commit
      checkout main
      commit tag: "main"
      checkout feature
      commit tag: "feature, HEAD"
  
  • Sur cet exemple, feature est la branche active.

Lister les branches d’un dépôt

  • Pour lister les branches d’un dépôt, la commande à utiliser est git branch :
    git branch
    
    * feature
      main
    
    • La branche actuellement active est signalée par l’astérisque (*)
    • L’option -v permet d’afficher en plus l’identifiant et le message du dernier commit de chaque branche

Créer une branche

  • La création de branche s’effectue également grâce à git branch :
    git branch <nom_de_la_branche> <commit_initial>
    
    • Si aucun commit initial n’est précisé, HEAD sera le point de départ de la nouvelle branche.
    • Le nom d’une branche :
      • ne peut contenir que des caractères ASCII
      • ne dois pas commencer par un tiret
      • ne dois pas contenir deux points consécutifs
      • ne dois pas se terminer par un slash, mais peut en contenir pour créer une hiérarchie

Changer de branche

  • git switch permet de changer la branche active :
    git switch <branche_a_activer>
    
    • Pour les versions de Git antérieures à 2.23.0, la commande était git checkout <branche>
    • Tous les nouveaux commits seront affiliés à la branche activée
    • Changer de branche :
      • Modifie le répertoire de travail et l’index
      • Préserve les modifications en cours
      • Déplace l’étiquette HEAD

Mettre de côté les modifications en cours

  • Il peut arriver que l’on souhaite temporairement mettre de côté des modifications en cours, par exemple pour travailler en urgence sur un problème.
  • Git rend cela possible grâce à la commande :
    git stash
    
    • Cette commande stocke temporairement les modifications du répertoire de travail et de l’index, et remet le répertoire de travail à l’état du dernier commit de la branche.
    • L’option --include-untracked permet d’également inclure les fichiers non suivis par Git.
  • Pour réappliquer dans la branche active les dernières modifications mises de côté, la commande est :
    git stash pop
    
  • Il est également possible de supprimer des modifications sans les appliquer :
    git stash drop
    
  • Enfin, il est possible d’accumuler plusieurs stashes, qui peuvent être listés :
    git stash list
    

Fusionner deux branches

  • Fusionner des branches permet d’intégrer des modifications faites sur une branche dans une autre.
    • C’est une opération cruciale, car elle permet par exemple d’intégrer à une version des productions des fonctionnalités développées en parallèle.
  • La fusion doit être initiée en ayant activé la branche cible (celle qui doit recevoir les modifications)
    • La fusion s’effectue avec la commande git merge, en précisant la branche source des modifications :
    git merge <branche_source>
    
    %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true }}}%%
    gitGraph
      commit
      commit id: "1-8a4f40a"
      branch feature
      commit
      commit
      checkout main
      commit
      checkout feature
      commit tag: "feature"
      checkout main
      merge feature id: "6-1a59d24" tag: "main, HEAD"
  
  • Git recherche l’ancêtre commun des branches à fusionner (ici, 1-8a4f40a) et y injecte les modifications issues de chaque branche, pour créer un nouveau commit dans la branche active, dit commit de merge.
    • Ici, le commit de merge a l’identifiant 6-1a59d24.
  classDiagram
    direction LR
    Commit1..Commit4
    Commit1..Commit5
    Commit4..Commit6
    Commit5..Commit6
    class Commit1 {
      fichier1.txt 41ad58
      fichier2.txt 1efc25
      fichier3.txt 690a41
    }
    class Commit4 {
      fichier1.txt 914da0
      fichier2.txt 1efc25
      fichier3.txt 690a41
    }
    class Commit5 {
      fichier1.txt 41ad58
      fichier2.txt ea451d
      fichier3.txt 690a41
    }
    class Commit6 {
      fichier1.txt 914da0
      fichier2.txt ea451d
      fichier3.txt 690a41
    }
  

Fusion Fast-Forward

  • Si le commit de merge calculé par Git est égal au dernier commit de la branche source (il a le même hash), alors Git évite de créer un doublon.
  • À la place, il procède à une fusion fast-forward en déplaçant l’étiquette de la branche cible sur le commit de la branche source.
    %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true }}}%%
    gitGraph
      commit
      commit
      branch feature
      commit
      commit
      checkout main
      merge feature tag: "main, feature, HEAD"
  

Conflits de fusion

  • Un conflit de fusion survient si la même zone d’un même fichier a été modifiée entre l’ancêtre commun d’une part et les branches source et cible simultanément d’autre part.
    • Une zone d’un fichier correspond généralement à une ligne et son voisinage immédiat.
  • Dans ce cas, Git suspend la fusion et le développeur doit trancher manuellement le conflit en indiquant quelle version conserver.
  • Git signale l’échec de la fusion automatique et les conflits dans la sortie de la commande git merge :

    Auto-merging sonnet.txt
    CONFLICT (add/add): Merge conflict in sonnet.txt
    Automatic merge failed; fix conflicts and then commit the result.
    
  • La commande git status précise également qu’une fusion est en cours et la liste des fichiers en conflit :

    On branch main
    You have unmerged paths.
    (fix conflicts and run "git commit")
    (use "git merge --abort" to abort the merge)
    
    Unmerged paths:
    (use "git add <file>..." to mark resolution)
    both added:      sonnet.txt
    
    no changes added to commit (use "git add" and/or "git commit -a")
    

Résoudre les conflits de fusion

  • Pour résoudre les conflits, le développeur doit ouvrir dans un éditeur de texte les fichiers concernés.
  • Git a inséré dans le fichier des informations sur les modifications effectuées dans les branches source et cible sous le format suivant :
    <<<<<<< HEAD
    Version de la branche cible
    =======
    Version de la branche source
    >>>>>>> source
    
    • Si une zone est vide, c’est qu’elle n’existe pas dans la branche concernée.
  • Il est possible, et souvent utile, d’afficher également des informations sur le contenu de l’ancêtre commun via une option de configuration de Git :
    git config --global merge.conflictstyle diff3
    
    • Dans ce cas, les informations sur les modifications adopteront la présentation suivante :
    <<<<<<< HEAD
    Version de la branche cible
    ||||||| merged common ancestor
    Version de l'ancêtre commun
    =======
    Version de la branche source
    >>>>>>> source
    
  • Une fois le fichier ouvert dans l’éditeur de texte, il est possible de reprendre à l’identique l’une des versions proposées, ou d’en écrire une nouvelle.
  • Lorsque tous les conflits d’un fichier sont résolus (il n’y a plus de marqueurs ajoutés par Git), le fichier doit être indexé (git add)
  • Après la résolution du dernier fichier, l’index peut finalement être commité (git commit), ce qui permettra de finaliser le commit de merge et la fusion des branches.
Exemple
  • Fichier sonnet.txt avant résolution des conflits :
<<<<<<< HEAD
=======
Quand l'écran s'allume, je tape sur mon clavier,
Et je me sens l'âme d'un héros bien armé,
Mais parfois c'est la guerre, les bugs sont aguerris,
Et je dois me résoudre à un peu de répit.

>>>>>>> alt-sonnet
Sur mon écran s'affichent des pixels brillants,
Des octets bien alignés, des programmes ardents,
Des virus dévastateurs, des spams envahissants,
Des câbles emmêlés, des bugs persistants.

<<<<<<< HEAD
Je pianote sur mon clavier, tel un virtuose,
Je créé des dossiers, des fichiers grandioses,
Je navigue sur le web, je me sens comme un rose,
Mais parfois je m'emmêle, je suis un peu névrose.

Je rêve de machines, de logiciels parfaits,
De claviers qui répondent à mes moindres souhaits,
De souris magiques qui font tout à ma place.
=======
Je rêve de machines, de logiciels parfaits,
D'ordinateurs qui pensent, qui lisent dans mes traits,
De robots programmés, qui m'apportent le café.
>>>>>>> alt-sonnet

Mais en attendant, je m'en remets à mon ordinateur,
Avec ses programmes parfois un peu farfelus,
Et je souris, car je sais qu'il est mon sauveur.
  • Fichier sonnet.txt après résolution des conflits :
    Quand l'écran s'allume, je tape sur mon clavier,
    Et je me sens l'âme d'un héros bien armé,
    Mais parfois c'est la guerre, les bugs sont aguerris,
    Et je dois me résoudre à un peu de répit.
    
    Je pianote sur mon clavier, tel un virtuose,
    Je créé des dossiers, des fichiers grandioses,
    Je navigue sur le web, je me sens comme un rose,
    Mais parfois je m'emmêle, je suis un peu névrose.
    
    Je rêve de machines, de logiciels parfaits,
    D'ordinateurs qui pensent, qui lisent dans mes traits,
    De robots programmés, qui m'apportent le café.
    
    Mais en attendant, je m'en remets à mon ordinateur,
    Avec ses programmes parfois un peu farfelus,
    Et je souris, car je sais qu'il est mon sauveur.
    
    • Le premier conflit a été résolu en conservant la version de alt-sonnet
    • Le second par une ré-écriture mélangeant les deux versions et supprimant un texte commun
  • La commande git status permet de confirmer que la fusion est terminée après commit des modifications :
    On branch main
    nothing to commit, working tree clean
    
  • En cas de difficultés, il est possible d’abandonner une fusion pour la recommencer ultérieurement ou avec d’autres branches :
    git merge --abort
    

Linéariser un historique

  • Pour des raisons de lisibilité, on peut vouloir rendre aussi linéaire que possible l’historique d’une branche, en particulier de la branche principale.
  • Git propose des approches permettant de récupérer tout ou partie des modifications d’une autre branche tout en conservant un historique clair.

Récupérer un commit : le cherry pick

  • Git permet de récupérer dans une branche les modifications apportées par un commit réalisé dans une autre branche avec la commande git cherry-pick :
    git cherry-pick <identifiant_commit>
    
    • En cas de conflit, la même mécanique de résolution des conflits que pour la fusion de branches sera déclenchée
    %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true }}}%%
    gitGraph
      commit
      commit
      branch feature
      commit id: "2-7ed128b" tag: "feature"
      checkout main
      commit
      cherry-pick id: "2-7ed128b"
      commit
      commit tag: "main, HEAD"
  
  • Le commit 2-7ed128b, initialement présent dans la branche feature a été intégré sans fusion à l’historique de la branche main.

Réécrire une branche dans une autre : le rebase

  • Le rebase est une autre façon de fusionner deux branches.
    • Au lieu d’écrire un commit de merge, le rebase consiste à intégrer tous les commits de la branche source dans la branche cible.
    • Une fois positionné dans la branche cible, la commande git rebase permet d’effectuer cette opération :
      git rebase <branche_source>
      
  • Les commits de la branche source seront réécrits dans la branche cible dans le même ordre, immédiatement après l’ancêtre commun.
  • Les conflits devront être résolus après chaque commit, pour permettre à l’opération de continuer :
    • Les fichiers en conflit doivent être ajoutés à l’index avec git add
    • Puis, il faut demander à Git de continuer le rebase avec la commande :
      git rebase --continue
      
Comparaison entre merge et rebase
  • Exemple d’historique après un git merge :
    %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true }}}%%
    gitGraph
      commit id: "0-71af11d"
      commit id: "1-8a4f40a"
      branch feature
      commit id: "2-9358c74"
      commit id: "3-8151b64"
      checkout main
      commit id: "4-09aa3e3"
      checkout feature
      commit id: "5-af29107" tag: "feature"
      checkout main
      merge feature id: "6-1a59d24"
      commit id: "7-46ccbcb" tag: "main, HEAD"
  
  • Le même historique après un git rebase:
    %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true }}}%%
    gitGraph
      commit id: "0-71af11d"
      commit id: "1-8a4f40a"
      commit id: "2-9358c74"
      commit id: "3-8151b64"
      commit id: "5-af29107"
      commit id: "4-09aa3e3"
      commit id: "6-1a59d24"
      commit id: "7-46ccbcb" tag: "main, HEAD"
  

Simplifier l’historique : le squash

  • Le squash permet à Git de rassembler plusieurs commits en un seul au moment d’une fusion, ce qui est une autre façon de linéariser un historique tout en le simplifiant.
    • La manière la plus simple d’y parvenir est d’ajouter l’option --squash à git merge:
      git merge --squash <branche_source>
      
    • Cette opération écrit dans l’index la combinaison de tous les commits de la branche source.
    • Il faut ensuite effectuer un commit classique pour terminer la fusion.

Supprimer une branche

  • L’option -d de git branch permet de supprimer une branche :
    git branch -d <branche_a_supprimer>
    
    • La suppression ne doit être effectuée que si la branche est devenue inutile :
      • Parce qu’elle a été fusionnées dans une autre
      • Parce que ses modifications ont été abandonnées (l’option devient alors -D par sécurité)
    • Une fois la branche supprimée, ses commits non fusionnés sont définitivement perdus

Travailler avec une forge logicielle

Se synchroniser avec un dépôt distant et travailler de manière collaborative, exploiter les fonctionnalités de la forge logicielle GitLab

Forge logicielle ?

  • Une forge logicielle est une plateforme web qui permet aux développeurs de gérer leur code source, de collaborer sur des projets de développement, de suivre les bugs et les problèmes, et de déployer des applications.
  • Outre le suivi de version basé sur Git, les forges logicielles incluent des fonctionnalités comme la création de merge requests, la gestion de bugs, la documentation, la gestion des tâches, les tests automatisés et les déploiements continus.
  • Il existe de nombreuses forges logicielles, les plus populaires aujourd’hui étant GitHub, GitLab et Bitbucket.
  • Cette formation va s’intéresser à GitLab, mais la plupart des fonctionnalités qui seront présentées se retrouvent sous des formes similaires sur les autres plateformes.
    • GitLab a l’intérêt d’avoir une version open source qui permet de l’installer de manière indépendante dans son propre environnement de production.

Dépôt distant ?

  • Un dépôt distant est un dépôt Git hébergé sur un serveur, permettant de centraliser et de redistribuer les modifications d’un projet à toute l’équipe.
  • Les forges logicielles incluent un serveur Git pour l’hébergement de dépôts distants.

Établir une connexion sécurisée SSH

  • Git peut utiliser plusieurs protocoles pour dialoguer avec un dépôt distant. Les plus fréquemment utilisés sont HTTPS et SSH.
  • GitLab prend en charge ces deux protocoles : HTTPS via l’utilisation de jetons d’accès et SSH via un système de clés asymétriques.
  • À l’usage, SSH est généralement le choix le plus simple et performant pour travailler de manière régulière depuis le même ordinateur.
  • Sa configuration demande de générer une paire de clés de chiffrement, et de communiquer la clé publique à GitLab.

Générer une paire de clés SSH

  • La génération d’une paire de clés SSH s’effectue à partir du terminal de l’ordinateur avec la commande ssh-keygen :
    ssh-keygen -t ed25519
    
    • ed25519 est l’algorithme de génération des clés, s’appuyant sur l’algèbre des courbes elliptiques. Il existe également rsa qui repose sur la théorie des nombres premiers.
  • Une fois la commande lancée, le terminal pose plusieurs questions :
    Generating public/private ed25519 key pair.
    Enter file in which to save the key (C:\Users\<Utilisateur>/.ssh/id_ed25519):
    
    • Appuyer sur “Entrée” pour valider le chemin par défaut, ou préciser un chemin
    Enter passphrase (empty for no passphrase):
    Enter same passphrase again:
    
    • Si l’ordinateur n’est pas partagé et correctement sécurisé, il est possible de ne pas saisir de mot de passe.
    • Sinon, le mot de passe saisi ici devra être entré à chaque utilisation de la clé.
  • Une fois les informations saisies, le terminal confirme la réussite de la création de la clé en donnant ses informations d’identification :
    Your identification has been saved in C:\Users\<Utilisateur>\.ssh\id_ed25519
    Your public key has been saved in C:\Users\<Utilisateur>\.ssh\id_ed25519.pub
    The key fingerprint is:
    SHA256:nmqJx3Wutsb8fe75FUDLYiQqJk4E6ZeWe1X6FdvDM+w <Utilisateur>@<Hostname>
    The key's randomart image is:
    +--[ED25519 256]--+
    | .o.     . . .   |
    | ..     ..o.o .  |
    |.  ooo .o  o*+   |
    | .o=o .o  .o.B.  |
    |  o.. . S . . +. |
    |   . . ..o.  E  .|
    |    .o =oo      .|
    |    . =.= ..  ...|
    |     o.oo+. .++..|
    +----[SHA256]-----+
    
  • L’empreinte à saisir dans le champ clé peut être obtenue en lisant le fichier de la clé publique dont l’emplacement a été donné précédemment :
    cat C:\Users\<Utilisateur>\.ssh\id_ed25519.pub
    
  • Le champ “Titre” peut être rempli librement avec un nom permettant de reconnaître la clé.
  • Le type d’utilisation “Authentification et Signature” permet d’également utiliser la clé pour signer de manière sécurisée ses commits.
  • La date d’expiration est une sécurité facultative pour forcer le renouvellement ponctuel des clés.

Créer un dépôt distant

  • La création d’un dépôt distant commence par la création d’un nouveau projet GitLab.
    • Dans un premier temps, il est conseillé de choisir “Create blank project” pour démarrer d’un dépôt vide.
Interface de création d'un projet vide sur GitLab
  • La case “Initialiser le dépôt avec un README” ne doit PAS être cochée si un dépôt local existe déjà.

Relier un dépôt local existant à un dépôt distant

  • git remote permet de gérer les dépôts distants. Par convention, le dépôt distant principal d’un projet s’appelle origin.
  • Pour déclarer un nouveau dépôt distant dans un dépôt local existant et lui envoyer tout le contenu actuel, trois commandes sont nécessaires :
    git remote add origin git@gitlab.com:<nom-utilisateur>/<slug-projet>.git
    git push -u origin --all
    git push -u origin --tags
    

Cloner localement un dépôt distant

  • Si le projet a d’abord été créé comme un dépôt distant, ou pour récupérer le code d’un projet déjà existant, Git permet également le cloner un dépôt distant.
  • Le développeur récupère alors une copie locale de l’intégralité des fichiers et de l’historique du projet. La commande à utiliser est git clone :
    git clone git@gitlab.com:<nom-utilisateur>/<slug-projet>.git
    
    • Le dépôt distant est automatiquement configuré comme origin.

Synchroniser le dépôt local et le dépôt distant

  • Puisque le dépôt local est une copie intégrale du dépôt distant, le développeur peut travailler en mode déconnecté.
  • Néanmoins, il est important de pouvoir régulièrement se synchroniser pour récupérer les modifications faites par les autres et envoyer les siennes.

Envoyer des modifications

  • La commande git push permet d’envoyer les modifications (les commits) effectuées localement dans une branche :
    git push
    
    • Git recherchera une branche portant le même nom que la branche locale sur origin et y enverra les modifications locales.
    • Si la branche a été créé localement, l’option --set-upstream permet de demander à Git de la créer sur le serveur.
  • Les opérations sur les tags doivent également être synchronisées avec le serveur grâce à git pull :
    git push --tags
    
  • Si des opérations de réécriture de l’historique ont été menées (comme un rebase), il faudra demander à Git de forcer cette réécriture sur le dépôt distant :
    git push --force-with-lease
    
    • Cette opération échouera si d’autres modifications ont été effectuées entre temps sur la branche, pour éviter tout risque de perte de code.
      • Il existe une option --force pour réécrire l’historique sans cette sécurité.

Récupérer des modifications

  • Récupérer des modifications revient à fusionner la branche distante dans la branche locale. Cette opération s’effectue grâce à git pull :
    git pull
    
    • Si on souhaite effectuer cette fusion avec un rebase, il suffit d’ajouter l’option --rebase.
    • Le pull est en fait l’enchaînement de deux opérations : fetch pour télécharger les modifications, puis merge pour les intégrer.

Rôles et permissions

  • Même lorsqu’un dépôt est public et peut être cloné librement, GitLab propose un système de contrôle d’accès permettant de donner des droits différenciés aux différents intervenants d’un projet.
  • Ces paramètres se règlent à l’échelle du projet dans les options relatives aux Membres.
  • Owner : Le propriétaire du projet est responsable du projet et a tous les droits sur celui-ci.
  • Maintainer : Les mainteneurs ont les mêmes droits que le propriétaire du projet, à l’exception de la suppression du projet ou de certains éléments.
  • Developer : Les développeurs peuvent créer des branches, effectuer des commits et des merge requests, et commenter les demandes de fusion. Par défaut, ils ne peuvent pas valider des demandes de fusion ou supprimer des branches protégées.
  • Reporter : Les rapporteurs peuvent créer et gérer des tickets, mais ne peuvent pas effectuer de modifications sur le code source.
  • Guest : Les invités ont un accès limité en lecture seule au projet et ne peuvent pas effectuer de modifications sur le code source. Ils peuvent être autorisés à créer des tickets.

Gestion des tickets

  • GitLab propose un système de gestion de tickets pour les évolutions fonctionnelles (Issue) et les bugs (Incidents).
  • Les tickets peuvent ensuite être visualisés en liste, ou sur un tableau du type Kanban.

Créer un ticket

Interface de création d'un ticket GitLab
  • Un ticket peut être relié à un jalon, par exemple une itération pour un projet Scrum.
  • Il peut également être associé à une étiquette dont la liste est définie à l’échelle du projet. Il existe deux approches courantes et complémentaires :
    • Par type de ticket : amélioration, correction de bug, accessibilité…
    • Par phase de travail : spécification, développement, recette…

Gérer un ticket

Interface de gestion d'un ticket GitLab
  • GitLab permet de décomposer un ticket en sous-tâches, mais aussi de l’associer à d’autres tickets.
  • Il est également possible de créer automatiquement une branche liée au ticket, ce qui permet de le clore une fois la branche fusionnée.
  • Des outils de gestion du temps (estimation de charge, date d’échéance) sont également proposés.

Branches protégées

  • GitLab inclut un mécanisme de protection des branches d’un dépôt.
    • Cette protection permet de contrôler qui peut envoyer ou fusionner du code dans certaines branches, notamment dans main.
    • La protection peut obliger à passer par les merge requests pour modifier le code d’une branche.
  • Il est toujours possible de faire des fusions ou des ajouts sur le dépôt local dans les branches protégées, mais les opérations de type git push seront alors systématiquement rejetées.

Merge Requests

  • Une merge request est une demande de fusion, c’est-à-dire que son auteur demande à ce qu’une branche (généralement celle sur laquelle il a travaillé) soit la branche source d’un git merge sur une branche cible protégée.
    • Selon la configuration du dépôt, la validation de cette demande peut être soumise à approbation préalable ou à la validation de tests.

Créer une merge request

Interface de création d'une merge request GitLab
  • La création d’une merge request demande au développeur de préciser les branches source et cible et de décrire ses modifications.
  • Il est possible de configurer une merge request pour qu’elle effectue automatiquement un squash lorsqu’elle est validée (“Écraser les commits lorsque la demande de fusion est validée”), ou pour supprimer la branche source.

Valider une merge request

Interface de validation d'une merge request GitLab
  • Lorsque le message “Ready to merge !” est visible sur une merge request, elle est prête à être validée et fusionnée.
    • L’option “Delete source branch” permet de supprimer automatiquement la branche source une fois l’opération terminée
    • Edit commit message permet de modifier le message du commit de merge, pour la lisibilité de l’historique.
  • Une fois la validation effectuée, l’exécution de la commande git pull permet de répercuter la fusion sur le dépôt local.

Flux de travail usuels

GitFlow, OneFlow, GitLab Flow

Flux de travail ?

  • Un flux de travail (en anglais, workflow) est une façon standardisée d’organiser les branches d’un projet Git.
  • Leur utilisation permet de bénéficier de méthodes éprouvées pour s’adapter à des modes usuels d’organisation et d’outils spécifiquement conçus pour leur prise en charge.

GitFlow

  • GitFlow est un flux de travail qui a été proposé par Vincent Driessen en 2010.
  • Son objectif est de permettre en parallèle la maintenance d’une version de production et l’élaboration d’une version de développement.
  • GitFlow s’appuie sur 5 familles de branches :
    • main : branche principale et permanente, chaque version de main est vouée à être mise en production
    • develop : branche permanente, soutenant la version de développement
    • feature : branches éphémères tirées de develop, dédiées au développement d’une fonctionnalité
    • release : branches éphémères tirées de develop, servant aux ultimes correctifs avant mise en production
    • hotfix : branches éphémères tirées de main, permettant la correction de bugs en production

Exemple avec GitFlow

    %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true }}}%%
    gitGraph
        commit tag: "v1.0.0"
        branch develop order: 3
        branch feature/US1 order: 4
        commit
        commit
        checkout main
        branch hotfix/1.0.1 order: 1
        commit
        checkout main
        merge hotfix/1.0.1 tag: "v1.0.1"
        checkout develop
        merge hotfix/1.0.1
        branch feature/US2 order: 5
        commit
        commit
        checkout feature/US1
        commit
        checkout develop
        merge feature/US1
        branch release/1.1.0 order: 2
        commit
        commit
        checkout main
        merge release/1.1.0 tag: "v1.1.0"
        checkout develop
        merge release/1.1.0
        checkout feature/US2
        commit
  

Utilisation de l’extension GitFlow

  • Les installations de Git pour Windows depuis la version 2.6.4 incluent directement une extension permettant une prise en charge simplifiée de GitFlow.
    • Pour macOS et Linux, un paquet nommé git-flow est généralement disponible pour installer cette extension.
  • L’extension se manipule ensuite en ligne de commandes au travers de l’instruction git flow

Initialisation

  • L’initialisation de GitFlow s’effectue en utilisant la sous-commande init de git flow :
    git flow init
    
  • Git demande alors des informations sur les noms à utiliser pour les différentes branches et les tags de version :
    Which branch should be used for bringing forth production releases?
    Branch name for production releases: [main]
    Branch name for "next release" development: [develop]
    How to name your supporting branch prefixes?
    Feature branches? [feature/]
    Release branches? [release/]
    Hotfix branches? [hotfix/]
    Version tag prefix? [] v
    

Développer une fonctionnalité

  • Pour démarrer une branche de fonctionnalité, il suffit d’utiliser l’instruction feature start :
    git flow feature start <nom_de_la_fonction>
    
    • Git va alors automatiquement créer une branche à partir de develop et l’activer.
  • Il est ensuite possible de publier cette branche sur un dépôt distant :
    git flow feature publish <nom_de_la_fonction>
    
  • Il est également possible de récupérer une branche de fonctionnalité à partir d’un dépôt distant :
    git flow feature pull origin <nom_de_la_fonction>
    
  • Enfin, une fois le développement d’une fonctionnalité terminée, il est possible de la clôturer :
    git flow feature finish <nom_de_la_fonction>
    
    • Cette commande fusionne la branche de fonctionnalité dans develop, supprime la branche devenue inutile et réactive develop.

Réaliser une version de production ou un correctif

  • Les mêmes commandes que pour les fonctionnalités peuvent s’utiliser pour les versions de production ou les correctifs, en remplaçant le mot-clé feature par release ou hotfix :
    git flow release start <numero_de_version> <commit_de_depart>
    
    • Le numéro de version saisi sera ensuite apposé comme un tag lors de la fusion dans main.
    • Il est possible de faire partir une release (resp., hotfix) de n’importe quel commit de develop (resp., main).

OneFlow

  • OneFlow est une évolution de GitFlow proposée par Adam Ruka en 2017.
  • Son objectif est de proposer un historique plus clair et lisible sur le long terme en ne se basant que sur une branche perpétuelle.
  • Utiliser OneFlow de manière efficace implique de manipuler les tags et les squash ou les rebase.
  • OneFlow s’appuie sur une branche principale : main qui sert de branche de développement
  • On dérive de cette branche trois types de branches éphémères :
    • feature pour les fonctionnalités
    • release pour les mises en production
    • hotfix pour les correctifs sur la production
  • Les branches release et hotfix s’achèvent par un tag qui permet d’identifier leur dernier commit comme étant prêt à être mis en production.

Exemple avec OneFlow

    %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true }}}%%
    gitGraph
        commit tag: "v1.0.0"
        branch hotfix/1.0.1 order: 1
        branch feature/US1 order: 3
        commit
        commit
        checkout main
        checkout hotfix/1.0.1
        commit
        checkout main
        merge hotfix/1.0.1 tag: "v1.0.1"
        branch feature/US2 order: 4
        commit
        commit
        checkout feature/US1
        commit
        checkout main
        merge feature/US1
        branch release/1.1.0 order: 2
        commit
        commit
        checkout main
        merge release/1.1.0 tag: "v1.1.0"
        checkout feature/US2
        commit
  
  • Pour améliorer encore la lisibilité de l’historique, il est conseillé de rebase les branches de fonctionnalité sur la dernière version étiquetée de main avant de les fusionner :
    %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true }}}%%
    gitGraph
        commit tag: "v1.0.0"
        branch hotfix/1.0.1 order: 1
        checkout main
        checkout hotfix/1.0.1
        commit
        checkout main
        merge hotfix/1.0.1 tag: "v1.0.1"
        branch feature/US1 order: 3
        commit
        commit
        commit
        checkout main
        merge feature/US1
        branch release/1.1.0 order: 2
        commit
        commit
        checkout main
        merge release/1.1.0 tag: "v1.1.0"
        branch feature/US2 order: 4
        commit
        commit
        commit
        checkout main
        merge feature/US2
  

GitLab Flow

  • GitLab Flow est une autre approche alternative à GitFlow qui a été proposé par les équipes de développement de GitLab en 2016.
  • Son approche combine celle des branches de fonctionnalité avec une prise en charge de multiples environnements de déploiement.
    • Elle est donc plus particulière adaptée aux grandes organisations qui ont un système informatique complexe.
  • Dans GitLab Flow, main est une branche perpétuelle qui sert au développement.
    • On en découle des branches de fonctionnalités éphémères.
  • Il existe également autant de branches permanentes que d’environnements de déploiement.
    • Les fonctionnalités et les correctifs se déplacent entre les branches par fusion.

Exemple avec GitLab Flow

    %%{init: {'theme': 'dark', 'themeVariables': { 'darkMode': true }}}%%
    gitGraph
        commit
        branch qualification order: 3
        commit
        checkout main
        branch production order: 5
        commit tag: "v1.0.0"
        checkout main
        branch feature/US1 order: 1
        commit
        commit
        checkout production
        branch hotfix/1.0.1 order: 4
        checkout hotfix/1.0.1
        commit
        commit
        commit
        checkout production
        merge hotfix/1.0.1 tag: "v1.0.1"
        checkout qualification
        merge hotfix/1.0.1
        checkout main
        merge qualification
        branch feature/US2 order: 2
        commit
        commit
        checkout feature/US1
        commit
        checkout main
        merge feature/US1
        checkout qualification
        merge main
        checkout production
        merge qualification tag: "v1.1.0"
        checkout feature/US2
        commit
        checkout main
        merge feature/US2
        checkout qualification
        merge main
  
  • Le GitLab Flow permet de maintenir plusieurs versions de recette et de production en parallèle, et de maîtriser les fonctionnalités proposées.
    • Mais, il demande de faire preuve de vigilance dans les opérations de fusion pour s’assurer qu’un correctif fait à un endroit a bien été répercuté partout où il devait l’être.
    • La lisibilité de l’historique peut également être améliorée en utilisant des rebase.

Remarques

  • Il existe bien d’autres flux de travail pour Git et aucun n’a vocation à être une solution universelle qui s’adapte à tous les environnements.
  • Il est même tout à fait possible de concevoir un flux “sur mesure” pour répondre à un besoin spécifique.
  • Le plus important est surtout que toute l’équipe qui travaille sur un projet ait connaissance du flux en vigueur et l’applique.

Git et l’intégration continue (CI)

Introduction aux pipelines GitLab CI/CD

Intégration continue ?

  • L’intégration continue (Continuous Integration, CI) est une pratique de développement logiciel qui consiste à tester et à construire automatiquement un projet chaque fois qu’un développeur soumet un changement dans le code source.
  • Cette pratique vise à détecter rapidement les erreurs de code et à garantir que le code est fonctionnel et prêt à être déployé.

Pipeline ?

  • Un pipeline d’intégration continue est un processus automatisé qui permet de construire, tester et déployer un projet.
  • Il s’agit d’une série d’étapes configurées pour être exécutées automatiquement par un runner lorsque des événements surviennent un dépôt (comme un commit).

GitLab CI/CD ?

  • GitLab CI/CD est l’outil permettant de configurer des pipelines d’intégration continue dans un projet géré sur GitLab.
    • Il s’appuie sur les runners GitLab Runner qui sont une technologie open source pouvant être déployée sur sa propre installation.

Configurer un pipeline GitLab CI/CD

  • La configuration d’un pipeline GitLab CI/CD repose sur la rédaction d’un fichier de configuration nommé “manifeste”.
  • Par convention, le manifeste se nomme .gitlab-ci.yml et doit être placé à la racine du dépôt.
  • Le manifeste est écrit en YAML, un langage de sérialisation de données généralement utilisé pour écrire des fichiers de configuration.

Exemple de syntaxe YAML

# Commentaire
cle: valeur
parent:
  enfant: valeur
liste:
  - option 1
  - option 2
niveau1:
  niveau2:
    niveau3a: valeur a
    niveau3b: |
      1 choix 1
      2 choix 2      
    niveau3c:
      - liste

Jobs

  • Un pipeline GitLab CI/CD est composé de jobs, qui sont un ensemble d’instructions de ligne de commande (un script) qui doivent être exécutées par le runner :
    job:nom-du-job:
      script: 
        - commande 1
        - commande 2
    
  • Par défaut, tous les jobs définis dans le manifeste s’exécutent simultanément.

Images

  • Les runners GitLab reposent sur une technologie de conteneurisation compatible avec Docker.
  • Il est donc possible de demander à un job de s’exécuter à partir d’une image Docker, pour éviter d’avoir à configurer à la main un environnement :
    job:build:
      image: maven/3-eclipse-temurin-17 # Maven 3 + Java 17
      script: 
        - mvn package
    

Étapes

  • Il est possible de regrouper des jobs par étapes, nommées stages qui s’exécuteront de manière séquencée.
    • Tous les jobs d’une même étape s’exécutent simultanément, puis GitLab passe aux jobs de l’étape suivante.
  • Le manifeste doit donc contenir une déclaration des étapes du pipeline, puis chaque job doit être rattaché à une étape.
stages:
  - build
  - test

job:build:
  image: maven/3-eclipse-temurin-17
  stage: build
  script:
    - mvn package
  
job:unit-test:
  image: maven/3-eclipse-temurin-17
  stage: test
  script:
    - mvn test

Règles de lancement conditionnel

  • Par défaut, GitLab CI/CD exécute le pipeline à chaque fois qu’un commit est envoyé.
  • Il est possible de fixer des conditions pour modifier ce comportement, par exemple pour n’exécuter certaines étapes que dans certaines branches.
  • Le mot-clé rules permet de fixer ces règles de lancement conditionnel pour un job.
  • Au sein du bloc rules, il est possible de fixer une ou plusieurs conditions avec if.
    • Il faut et il suffit qu’une des conditions soit respectée pour qu’un job puisse être exécuté.
  • Plusieurs variables usuelles sont disponibles pour rédiger ces conditions :
    • $CI_COMMIT_BRANCH : branche du commit
    • $CI_COMMIT_TAG : tags du commit
    • $CI_COMMIT_TITLE : message du commit
job:deploy:
  image: maven/3-eclipse-temurin-17
  script: 
    - mvn deploy
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
    - if: $CI_COMMIT_TITLE =~ /Deploy.*/
  • Dans cet exemple, le job deploy ne s’exécutera que si le pipeline est déclenché depuis la branche main ou si le message de commit débute par “Deploy”.

Modes d’exécution

  • Les modes d’exécution sont une autre façon de conditionner le lancement d’un job.
  • Par défaut, les jobs d’une étape ne se lancent que si tous les jobs de l’étape précédente se sont terminés avec succès.
  • L’option when permet de modifier ce comportement.
    • when peut être combiné avec rules pour créer des scénarios d’exécution complexes.
  • Plusieurs modes d’exécution sont disponibles :
    • on_success : seulement si tous les jobs de l’étape précédente réussissent (mode par défaut)
    • on_failure : seulement si au moins un job de l’étape précédente a échoué
    • always : toujours exécuter ce job
    • never : ne jamais exécuter ce job (utile en combinaison avec rules)
    • manual : ne pas exécuter automatiquement ce job, mais permettre de le lancer manuellement
  • Il est également possible de rendre l’échec d’un job non bloquant avec allow-failure: true
job:deploy:
  image: maven/3-eclipse-temurin-17
  script: 
    - mvn deploy
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
    - if: $CI_COMMIT_TITLE =~ /Deploy.*/
      when: manual
  • Désormais, le job deploy ne se lancera plus automatiquement si le commit commence par “Deploy”, mais une option permettant de le lancer manuellement sera proposée à la place.

Cache

  • Le cache permet de sauvegarder des fichiers/dossiers d’un runner et de les partager tout au long du pipeline.
    • Il est détruit une fois le pipeline terminé.
  • L’utilisation du cache permet par exemple d’éviter de rebuild plusieurs fois un même projet entre les différentes étapes du pipeline.
  • Un bloc cache doit préciser les fichiers et dossiers concernés dans le sous-bloc paths.
  • Par défaut, le job commencera par récupérer les fichiers du cache au début de son exécution, et écrira les modifications à la fin.
    • Ce comportement peut être modifié avec l’option policy :
      • pull : récupérer le cache, mais ne pas écrire les modifications
      • push : ne pas récupérer le cache, mais écrire les modifications
stages:
  - build
  - verify

job:build:
  image: maven/3-eclipse-temurin-17
  stage: build
  script:
    - mvn package
  cache:
    paths:
      - target
    policy: push
  • À l’issue de l’exécution de l’étape build, le dossier target sera mis en cache pour être réutilisé à l’étape verify.

Artefacts

  • Les artefacts ont un fonctionnement proche du cache, à ceci près qu’ils sont préservés à l’issue du pipeline.
  • Ils peuvent donc être réutilisés dans un autre pipeline, publiés ou bien encore téléchargés.
  • Les artefacts se configurent grâce à un bloc artifacts.
  • Le bloc artifacts se décompose en plusieurs sous-blocs :
    • paths permet de définir les fichiers et dossiers du runner à rendre disponibles
    • name définit le nom de l’archive zip qui contiendra l’artefact
    • expire_in fixe la durée de vie de l’artefact. Par défaut, cette durée s’exprime en secondes, mais il est possible de préciser une autre unité.
stages:
  - build
  - verify

job:build:
  image: maven/3-eclipse-temurin-17
  stage: build
  script:
    - mvn package
  artifacts:
    paths:
      - target
    name: build
    expire_in: 1 week
  • Ce job rendra le dossier target disponible au téléchargement dans un fichier nommé build.zip. Ce fichier sera conservé par GitLab pendant 1 semaine.

Superviser un pipeline GitLab

  • Dès qu’un pipeline est configuré sur un dépôt GitLab, une icône apparaît en regard de chaque commit.
    • Cette icône indique le résultat du pipeline :
      • Une coche verte indique un pipeline réussi
      • Un point d’exclamation jaune indique une réussite avec des avertissements
      • Une croix rouge signale un pipeline en échec
      • Un rond bleu signifie que le pipeline est en cours d’exécution
      • Une flèche grise invite à exécuter une étape en mode manuel

Détails d’un pipeline

Détail d'un pipeline GitLab
  • En cliquant sur l’icône, on peut accéder à l’interface détaillée du pipeline :
    • Chaque colonne correspond à une étape
    • Chaque pastille correspond à un job, avec son résultat d’exécution

Détails d’un job

Détail d'un job GitLab
  • GitLab permet d’accéder aux détails d’exécution d’un job, et notamment aux logs du runner.
    • Cette interface permet également de télécharger les artefacts.