Révisions GIT: épisode 1

Avec des outils comme Hugo, et son écosystème, une bonne connaissance de git devient vite indispensable. Il se trouve que ce sujet est également apparu côté professionnel. J’avais quelques lacunes sur le sujet : j’en ai donc profité pour me remettre à niveau. J’entame une série d’articles sur cet outil, en démarrant par quelques concepts, et un exemple.

Révisions GIT: épisode 1

L’objectif de cet épisode 1 est de parcourir les concepts proposés par GIT, à travers une session de développement. Nous commencerons à voir des commandes, du vocabulaire, sans rentrer trop dans les détails, qui seront ensuite donnés dans les épisodes suivants.

Historique

On peut classer GIT dans la catégorie des gestionnaires de versions, ou VCS (Version Control System). L’objectif est de gérer les différentes versions d’un code source (ou d’autres fichiers), durant l’ensemble de son cycle de vie.

GIT a été créé en 2005 par Linus Torvalds, pour le développement de noyau Linux. L’équipe de développeurs du noyau Linux utilisait depuis plusieurs années un logiciel de SCM (Source Code Management) appelé BitKeeper. C’est en rendant, le code de BitKeeper publique, qu’est né Git-SCM.

Il existe deux types de gestionnaire de versions

  • Les gestionnaires centralisés (Centralized version control system - CVCS),
  • Les gestionnaires distribués (Distributed / Decentralized version control system - DVCS).

Contrairement à la majorité de ces prédécesseurs, GIT est un gestionnaire distribué. Chaque développeur dispose sur sa machine de son propre dépôt de source (repository), mais chacun garde la possibilité de “pousser” son code vers un dépôt distant / centralisé (remote repository).

Figure 1 : Principe d’un gestionnaire distribué
Figure 1 : Principe d’un gestionnaire distribué

1. L’installation et configuration

Démarrons donc notre périple : nous avons une application à développer, ou du contenu à écrire, et nous souhaitons démarrer ce projet avec un outil de versionning. Nous sommes pour l’instant sur un ordinateur individuel.

La première étape est d’installer l’outil GIT lui-même. Il faut donc

  • Télécharger l’outil depuis le site Git,
  • Installer l’outil (je ne détaille pas cette étape), en prenant les options par défaut.

Une fois le produit installé, il n’y a quasiment pas de configuration à faire, l’outil peut être utilisé directement. Il est cependant recommandé de configurer notre nom (ou un pseudo, et notre adresse de messagerie, et l’éditeur que l’on souhaite utiliser.

$ git config --global user.name "Alfred Dupont"
$ git config --global user.email "alfred@dupont.com"

$ git config --global core.editor "code --wait"  # Configuration de Visual Studio Code

Une liste de configurations pour un grand nombre d’éditeurs est disponible sur le site de GIT

2. Création d’un dépôt

Les outils sont prêts, passons à l’étape suivante, en créant le projet proprement dit :

$ mkdir <nom du projet>  # Creation d'un répertoire,
$ cd <nom du projet>     # On se positionne dans le répertoire,
$ git init               # Création du dépôt.

Si l’on regarde le contenu du répertoire <nom du projet>, on s’aperçoit que la commande à créer un répertoire .git.

Une autre façon de faire est de “cloner” un projet existant, qui se trouve sur un dépôt distant :

$ git clone https://github.com/egeorjon/Minimal-Blank minimal
    Cloning into 'minimal'...
    remote: Enumerating objects: 1422, done.
    remote: Counting objects: 100% (1422/1422), done.
    remote: Compressing objects: 100% (1056/1056), done.
    remote: Total 1422 (delta 530), reused 1218 (delta 332), pack-reused 0 eceiving objects: 100% (1422/1422), 5.86 MiB | 13Receiving objects: 100% (1422/1422), 5.92 MiB | 262.00 KiB/s, done.

    Resolving deltas: 100% (530/530), done.
    Updating files: 100% (738/738), done.

La syntaxe est git clone <adresse des dépôts distants> <répertoire>. Le répertoire indiqué, désigne le répertoire dans lequel sera téléchargé le code source. La commande va télécharger tous les éléments se trouvant dans un dépôt distant (Github, Gitlab, SourceForge, …).

Avant d’aller plus loin, nous avons quelques explications à donner :

  • En tant que développeur, nous allons créer / éditer / effacer des fichiers, qui stockés dans une arborescence,
  • GIT va “traquer” ces changements, et les stocker dans une sorte de structure parallèle, que l’on appelle dépôt.

Le point de départ est donc une arborescence de fichiers. Dans cette arborescence, nous avons des fichiers qui peuvent être

  • Non-suivis (untracked) : ces fichiers ne sont pas suivis par GIT, et ne peuvent donc pas être manipulés par l’outil,
  • Suivis (tracked) : GIT est capable de détecter chacune des modifications de ces fichiers (grâce à un mécanisme de checksum),
  • Modifiés (modified/dirty) : on retrouve ici, l’ensemble des fichiers qui ont été modifiés, mais qui n’ont pas encore été indexés,
  • Indexés (staged) : Il s’agit de fichiers qui ont été modifiés, et placés dans une zone particulière que l’on pourrait appeler zone de transit (staging area), en attente de validation (commit)
  • Validés (commited) : les fichiers validés sont ceux dont nous souhaitons figer / conserver les modifications.

GIT considère trois zones de stockage pour ces fichiers : Le working directory (répertoire de travail), la staging area (zone transitoire), et le Git repository (dépôt Git).

Figure 2 : Les 3 zones de Git
Figure 2 : Les 3 zones de Git

Bien comprendre ou se trouvent les fichiers que l’on souhaite manipuler, est essentiel pour bien utiliser GIT. La figure figure 3 résume ce qu’il s’est passé pour l’instant, avec notre commande git init.

Figure 3 : Création d’un dépôt local
Figure 3 : Création d’un dépôt local

3. Le processus de base

Le processus très simplifié avec GIT est décrit dans le schéma suivant :

  1. Le développeur crée, édite, efface, déplace des fichiers,
  2. Quand une série de modifications lui semble cohérente, et stable, le développeur place les fichiers dans l’index (la zone staging),
  3. Il peut alors vérifier l’ensemble des modifications effectuées,
  4. Avant de réaliser les commits nécessaires, en une seule ou plusieurs fois.
  5. Le développeur repasse alors à l’étape 1.

Pour ces étapes, nous utilisons principalement deux commandes : git add pour pousser les modifications dans la zone staging (indexation), et git commit pousser les fichiers dans le dépôt (validation) :

Figure 4 : Commandes git add, et git commit
Figure 4 : Commandes git add, et git commit

En itérant le processus précédent, nous obtenons la figure 5 :

Figure 5 : Le processus de base dans GIT
Figure 5 : Le processus de base dans GIT

Dans la figure 5, HEAD est le pointeur vers le dernier commit, ou plus exactement, vers le parent du prochain commit.

4. Les branches

Le processus décrit précédemment est linéaire. Mais lors d’un développement, nous pouvons avoir besoin

  • De mettre de côté le code en cours, pour explorer de nouvelles pistes, entamer une phase de développement un peu plus longue que d’habitude,
  • D’arrêter le développement en cours, pour corriger un bug, et donc développer un patch,
  • De développer une nouvelle fonctionnalité en parallèle du développement en cours.

C’est là que la notion de branche entre en jeux.

Création d’une branche

Par défaut, un dépôt possède une branche principale unique qui s’appelle main (précédemment master). Mais nous pouvons

  • Créer des branches grâce à la commande git branch <nom de la branche>,
  • Rendre une branche active : git checkout <nom de la branche>.

Figure 6: Création de la branche feature
Figure 6: Création de la branche feature

Même s’il ne se passe finalement rien, on commence ici, à mieux comprendre le rôle du pointeur HEAD : ce pointeur indique le parent du prochain commit. Dans le cas de la Figure 5, nous étions sur la branche main, donc le prochain commit sera sur cette branche. Sur la Figure 6, après le checkout, HEAD est attaché à la nouvelle branche feature. Le prochain commit s’effectuera sur la branche feature.

Développement

Continuons notre développement

Figure 7 : Développement sur deux branches
Figure 7 : Développement sur deux branches

Dans la Figure 7, nous faisons progresser notre développement en suivant deux branches : main et feature. Nous avons bien une version v4 dans la branche main, et une version v3 dans la branche feature, et donc des développements qui peuvent continuer leurs évolutions en parallèle.

A noter qu’en général, nous n’effectuons pas directement de développement dans la branche main, mais dans des branches annexes.

Fusion de deux branches

A un moment donné, si nous sommes satisfaits des développements de la branche feature, nous pouvons les intégrer à la branche main. On parle de fusion de branches (merge). Lors d’une fusion, GIT va intégrer toutes les modifications contenues sur chaque branche dans une seule et même arborescence.

Figure 8 : Fusion de deux branches
Figure 8 : Fusion de deux branches

Le processus de fusion habituel est

  • On se place d’abord sur la branche qui va recevoir toutes les modifications (dans notre cas git checkout main),
  • On effectue la fusion : git merge feature,
  • A partir de ce moment, toutes les modifications de la branche feature sont intégrées à la branche main, nous pouvons donc effacer la branche feature.

L’opération de fusion créé un nouveau commit, qui a deux parents (dans le cas d’un true merge).

La fusion décrite correspond à l’intégration des modifications liées à une branche, dans une autre. Il existe d’autres méthodes permettant de remplacer une partie des modifications d’une branche, par les modifications effectuées dans une autre.

Gestion des conflits

Une fusion correspond à l’intégration de toutes les modifications effectuées sur les deux branches que nous souhaitons fusionner. GIT est capable d’identifier les modifications dans les différents fichiers, et de les agréger. Dans certains cas, cependant, GIT ne peut pas gérer certains “conflits”, qu’il faut alors résoudre manuellement.

Pour cela GIT interrompt le processus de fusion, et ajoute des marqueurs dans les fichiers à fusionner. Nous devons éditer les fichiers manuellement afin de résoudre ces conflits. Cette méthode est peu pratique. Heureusement, il existe des solutions plus ergonomiques, GIT étant intégré à un grand nombre d’environnements de développements.

Figure 9 : Détection des différences avec Visual Studio Code
Figure 9 : Détection des différences avec Visual Studio Code

5. Les dépôts distants

Nous arrivons à la fin de notre journée de développement. Puisque nous travaillons en équipe, il est temps de partager notre travail avec les collègues. Pour cela, nous allons “pousser” tous les commits d’une branche vers un dépôt distant :

$ git push origin feature
    Enumerating objects: 25, done.
    Counting objects: 100% (25/25), done.
    Delta compression using up to 4 threads
    Compressing objects: 100% (14/14), done.
    Writing objects: 100% (14/14), 1.52 KiB | 172.00 KiB/s, done.
    Total 14 (delta 11), reused 0 (delta 0), pack-reused 0
    remote: Resolving deltas: 100% (11/11), completed with 10 local objects.
    remote: This repository moved. Please use the new location:
    remote:   https://github.com/egeorjon/Minimal-Blank.git
    To https://github.com/egeorjon/minimal-blank
    62fc137..820b8d5  feature -> feature

La commande git push va envoyer vers le dépôt distant désigné par origin, l’ensemble des modifications effectuées dans la branche feature. Vos collègues développeurs vont donc pouvoir voir votre code, et l’utiliser.

Figure 10 : Push des modifications locales vers le dépôt distant
Figure 10 : Push des modifications locales vers le dépôt distant

Fin de l’épisode 1

Cet épisode nous a permis de voir les grands principes d’utilisation de Git : Nous venons de voir notamment, la définition d’un dépôt, les trois zones de stockage, la notion de branche, ainsi que les dépôts distants.

La figure 11 résume les commandes utilisées dans cet épisode :

Figure 11 : Opérations de base
Figure 11 : Opérations de base

Commentaires