Série Git - Fonctionnement (2/5)

Comment Git fonctionne ?

Après avoir présenté Git et ses avantages, nous allons nous concentrer dans ce deuxième article sur la manière dont Git fonctionne.

Comme nous l’évoquions dans le premier chapitre de cette série, Git suit les modifications apportées aux fichiers du projet. Ainsi, chacun d’eux est considéré dans un des 4 états ci-dessous :

  • Untracked (non suivi) : Fichier qui n’est pas suivi par Git et dont les versions ne sont pas historisées.
  • Unmodified (non modifié) : Fichier suivi dont la version courante n’a pas été modifiée par rapport au dernier point de validation.
  • Modified (modifié) : Fichier suivi dont la version courante a été modifiée par rapport au dernier point de validation.
  • Staged (indexé) : Fichier suivi dont les modifications de la version courante seront référencées dans le prochain point de validation.

Les passages d’un état à l’autre peuvent être décrit par le schéma ci-dessous :

%%{init: { 'logLevel': 'debug', 'theme': 'dark', 'sequenceConfig': {}} }%% sequenceDiagram participant Untracked participant Unmodified participant Modified participant Staged Untracked->>Staged: Add a file (git add <filename>) Unmodified->>Modified: Edit a file (vi <filename>) Modified->>Staged: Stage a file (git add <filename>) Staged->>Unmodified: Commit a file (git commit <filename>) Unmodified->>Untracked: Remove a file (git rm <filename>)

Pour son fonctionnement local, Git considère essentiellement 3 zones de travail :

  • Working directory : Dossier de travail où se trouvent les sources.
  • Staging area : Emplacement temporaire où sont stockées les versions avant commit.
  • Local repository (dossier .git) : Historique des modifications.
Remarque

À noter qu’en plus des 3 zones de travail décritent ci-dessous, il en existe 2 autres :

  • remote : Copie locale des répos distants (lorsque vous synchronisez votre projet avec GitHub, GitLab ou tout autre serveur Git distant).
  • stash : Emplacement temporaire permettant de “mettre de côté” les fichiers à l’état modifiés.
%%{init: { 'logLevel': 'debug', 'theme': 'forest', 'flowchart': {}} }%% flowchart TB A("Working directory") B("Staging area") C(".git directory") A-- Stage Fixes -->B B-- Commit -->C C-- Checkout the project -->A
Avertissement
La zone de travail Local repository (dossier .git) est théoriquement “immuable” !
Danger
Ne pas inscrire d’information sensible dans un commit car elle pourra être retrouvée dans l’historique (sauf utilisation git filter-branch ou BFG Repo-Cleaner).

Le fichier .gitignore est un fichier permettant d’exclure certains fichiers du suivi. Il joue le rôle de filtre lors des commandes git.

Exemples de cas d’usage :

  • Fichiers de configuration ;
  • Fichiers binaires ;
  • Fichiers avec des données sensibles ;
  • Etc.

Exemple de contenu de fichier .gitignore :

1
2
3
./build/
./logs/
./password.txt
Astuces

Bonnes pratiques :

  • N’utiliser qu’un seul “.gitignore” à la racine de repo
  • Privilégier l’utilisation du chemin complet plutôt que seulement le nom du fichier

Les commits constituent la base du fonctionnement de Git. Ils consistent en des points d’arrêt dans l’historique des modifications apportées aux fichiers. Chaque commit est stocké de manière “définitive” dans le repository.

Les commits sont souvent représentés comme un graphe orienté, chacun des noeuds de ce graphe représentant un commit :

%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {}} }%% gitGraph commit commit

Chaque commit contient les informations suivantes :

  • Modifications apportées aux fichiers suivis par rapport à la version précédente
  • La référence du précédent commit
  • Les infos (nom/email) de l’auteur et la date
  • Les infos (nom/email) du committer et la date (Permet de distinguer la dernière modif d’un commit en cas de réécriture d’historique via amend ou rebase.)
  • Le commentaire associé au commit

Chacun de ces commits est référencé par un hash unique.

Exemple de hash : 6c78e246f967a554d1b9bbc64573f12ad9e9e32c (hash raccourci : 6c78e24).

Afin de faciliter la bascule entre les commits et les branches, Git dispose des pointeurs. Ces pointeurs agissent comme des alias faisant référence à des hash de commit.

Il existe plusieurs types de pointeurs :

  • HEAD
  • Les branches
  • Les tags

Le pointeur HEAD est un pointeur vers le commit courant. Cela veut donc dire que lorsque vous vous déplacez dans l’historique (avec la commande git checkout), le pointeur HEAD va pointer vers un commit différent.

Par défaut, celui-ci est positionné sur le dernier commit d’une branche.

Astuces

Le pointeur HEAD a pour avantage de permettre des références relatives.

Exemples :

  • HEAD représente le commit courant.
  • HEAD~ et HEAD~1 représentent l’avant-dernier commit.
  • HEAD~~ et HEAD~2 représentent l’avant avant-dernier commit.

Comme dit précédemment, les branches ne sont techniquement que des pointeurs vers des commits. Leur objectif est d’isoler les commits durant la phase de développement pour ne pas impacter les autres développeurs.

Sur le graphe ci-dessus, nous pouvons constater les deux branches main et feature représentées :

%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {}} }%% gitGraph commit branch feature checkout feature commit checkout main commit checkout feature commit

Bien que la branche main (anciennement master) soit souvent considérée comme une branche particulière, puisque créée par défaut, son fonctionnement est exactement le même que pour les autres branches.

À l’exception de certaines branches comme la branche main (ou develop dans le cas de l’utilisation de Git flow), les branches n’ont généralement qu’une existence temporaire et ont vocation à être fusionnées avec la ou les branches principales.

À noter que comme pour le pointeur HEAD, le pointeur de la branche va être mis à jour à chaque commit pour pointer vers le dernier.

Une fois encore, les tags ne sont que des pointeurs vers un commit au même titre que les branches ou le pointeur HEAD. L’unique différence avec eux est que les tags sont des pointeurs “permanents” vers un commit.

Cela veut donc dire qu’un tag ne change pas de destination lors d’un nouveau commit.

Les tags sont la plupart du temps utilisés pour représenter les versions d’un développement.

%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {}} }%% gitGraph commit commit tag: "1.0.0" commit

Comme expliqué en introduction, Git est un outil distribué. Les remotes correspondent donc à des copies distantes du répertoire.

Exemples de remote :

  • GitHub
  • Gitlab

L’objectif d’un remote comme GitLab est de servir d’interface entre les développeurs pour mettre en commun les modifications opérées. Il permet aussi l’ajout de fonctionnalités de type gestion des partages, des issues, des pull request, etc.