26 juin 2020

Git: une introduction

Que vous soyez dev ou ops, Git est devenu aujourd’hui indispensable. J’expliquerai dans cet article dont la cible sont les personnes débutants avec l’outil les commandes et le fonctionnement de base de Git. Dans l’article suivant, je montrerai des utilisations avancées.

Introduction

Git est un logiciel de gestion de versions distribué. Créé par Linus Torvalds pour la gestion des sources de Linux, il s’est imposé face à ses concurrents et est aujourd"hui l’outil de gestion de versions le plus utilisé.

Dans Git, le code source des projets est stocké sur la machine de chaque personne travaillant sur le projet et sur des dépots distants nommés "remote".

Chaque modification est stockée sous la forme d’un "commit", qui correspond à l’état de votre projet à un instant T (vous pouvez retrouver plus d’informations sur le fonctionnement bas niveau de Git dans la documentation).

Commandes

Git init

Tout d’abord, créez un répertoire nommé "test" sur votre machine. Naviguez dans ce répertoire avec votre terminal et exécutez git init:

$ git init
Dépôt Git vide initialisé dans /home/mathieu/prog/test/.git/

Cette commande initialise un projet git. Vous pouvez d’ailleurs voir qu’un répertoire .git a été créé à la racine du répertoire.

Git add et status

Créons maintenant un fichier, et lançons la commande git add dessus:

$ echo "test" > foo
$ git add foo
$ git status
Sur la branche master

Aucun commit

Modifications qui seront validées :
  (utilisez "git rm --cached <fichier>..." pour désindexer)

	nouveau fichier : foo

Vous vous pouvez le voir, nous utilisons aussi la commande git status pour voir l’état actuel de notre projet. On voit ici que le nouveau fichier a été ajouté à l’index. Cela veut dire que ce fichier est prêt à être commité, mais rien d’autre. Cela sert juste d’indication à git commit.

A noter: git add . vous permet d’ajouter tous les fichiers en cours de modification à l’index.

Créons maintenant un commit.

Git commit

Exécutez la commande suivante:

git commit -m "first commit" -m "ceci est mon premier commit git"

Je crée ici un commit dont le titre sera "first commit" et la description "ceci est mon premier commit git". La description est optionnelle ici, mais c’est une bonne pratique d’ajouter une description détaillée à chaque commit.

Vous pouvez aussi configurer git pour ouvrir votre éditeur sur la commande git commit, ce qui vous permet d’écrire plus facilement la description (sur plusieurs lignes par exemple). Dans le fichier ~/.gitconfig de votre machine (où se configurent les options génériques de Git), ajoutez:

[core]
    editor = emacsclient

Ici, la commande emacsclient (qui m’ouvre le fichier dans Emacs) sera automatiquement appelée lors d’une action Git nécessitant un éditeur. Vous pouvez remplacer cette valeur par votre éditeur préféré.

Regardons maintenant l’état de notre projet avec git status, et git log qui permet de voir notre historique Git:

$ git status
Sur la branche master
rien à valider, la copie de travail est propre

$ git log
commit f87ebd782f2b4512b0c192e56d310c8bb618dfc8 (HEAD -> master)
Author: mcorbin <corbin.math@gmail.com>
Date:   Thu Jun 25 23:04:21 2020 +0200

    first commit

    ceci est mon premier commit git

On voit que notre fichier a été commité. On voit que notre commit est associé à un hash (f87ebd782f2b4512b0c192e56d310c8bb618dfc8).
Les 7 premiers caractères des hash (f87ebd7 dans notre) sont souvent utilisés en abbréviation dans l’outillage ou certaines commandes git.

Voici donc l’état de notre projet local:

Premier commit

On a donc maintenant un commit sur notre branche principale (appelée master). Nous verrons plus tard ce qu’est une branche.

Remote

Poussons maintenant notre travail sur un dépôt distant. Par exemple, créez un projet sur Github (ou Gitlab, bitbucket…​)

Ici, je vais créer par exemple un projet nommé "test1" sur mon compte Github. Une fois le projet créé, je vais l’ajouter en tant que "remote" sur mon projet:

git remote add origin git@github.com:mcorbin/test1.git

Je crée ici un remote appelé origin. La commande git remote (qui supporte une option -v pour avoir plus d’informations`) vous permet de lister les remotes de votre projet.

Poussons maintenant notre code sur ce remote:

git push -u origin master
Énumération des objets: 3, fait.
Décompte des objets: 100% (3/3), fait.
Écriture des objets: 100% (3/3), 620 bytes | 620.00 KiB/s, fait.
Total 3 (delta 0), réutilisés 0 (delta 0)
To github.com:mcorbin/test1.git
 * [new branch]      master -> master
La branche 'master' est paramétrée pour suivre la branche distante 'master' depuis 'origin'.

Ici, nous poussons notre état local sur le dépôt "origin" et sur la branche "master". L’option -u nous permet d’indiquer à Git que notre branche "master" locale correspond à notre branche "master" distante.

Mais qu’est ce qu’une branche ?

Branches

La commande git branch permet de lister vos branches. Seulement "master" existe pour l’instant.
Créons une nouvelle branche appelée "my-feature" avec git branch my-feature. Votre nouvelle branche est créée.

Les branches permettent de travailler en isolation de la branche principale de votre projet. Vous pouvez, comme sur master, créer des commits, push vos modifications sur une branche distante…​ sans risquer de tout casser. Pour cela, vous devez commencer par vous "déplacer" sur votre branche nouvellement créée.

La comande git checkout permet cela. Ici, git checkout my-feature vous positionnera sur votre nouvelle branche dans votre projet local. Vous pouvez vérifier cela avec git status ou git branch à tout moment si vous avez un doute sur le positionnement de votre projet.

Note: la commande git checkout -b <nom_de_branche> permet de faire les deux actions précédentes en une commande.

Une branche est tout simplement un pointeur sur un commit. Dans notre cas, nous venons de créer notre branche, et donc nos branches "master" et "my-feature" pointent sur notre seul et unique commit.

Créons maintenant un nouveau fichier et puis commitons le dans notre branche:

$ echo "hello" > babar
$ git add .
$ git commit -m "nouveau commit sur ma branche"
[my-feature 7cd6872] nouveau commit sur ma branche
 1 file changed, 1 insertion(+)
 create mode 100644 babar

Si vous exécutez git log, vous verrez votre nouveau commit. Comme dit précédemment, la branche "master" n’a pas bougée, mais la branche "my-feature" pointe maintenant sur ce nouveau commit.

ma nouvelle branche my-feature et son commit

Vous pouvez maintenant pousser sur votre dépôt distant votre nouveau commit et votre branche avec git push -u origin my-feature.

Merges de branches

Nous allons maintenant ajouter le travail que nous avons fait sur la branche "my-feature" sur la branche master. Si vous utilisez un outil comme Github, cela peut être fait via l’interface de Pull Request. Ici, nous le ferons en ligne de commande.

Retournons sur la branche "master" avec git checkout master, et importons notre commit de la branche my-feature:

git merge my-feature
Mise à jour f87ebd7..7cd6872
Fast-forward
 babar | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 babar

Vous pouvez voir avec git log que le commit de notre branche a été rajouté à master.

La mention de Fast-forward indique que Git s’est contenté d’appliquer le commit de la branche "my-feature" au dessus de "master". Cela est possible car le travail était linéaire. Notre branche "master" n’avait pas bougée, et le commit de la branche "feature" (dont la source était master) a simplement été rajouté par Git sur "master":

Exemple de fast forward

Cela serait différent dans ce cas là:

# Création et déplacement sur une nouvelle branche
git checkout -b fix-bug

# Création d'un fichier et commit sur cette branche
echo "f1" >> f1 && git add f1 && git commit -m "f1"

# Retour sur master
git checkout master

# Création d'un ouveau commit sur master
echo "f2" > f2 && git add f2 && git commit -m "f2"

# Merge de notre branche "fix-bug" sur master
git merge fix-bug

A ce moment, Git va créer un nouveau commit, appelé commit de merge (et vous demander d’écrire son message).

En effet, la branche "master" a changée depuis le moment de la création de "fix-bug", et dans ce cas Git crée un nouveau commit (qui est la combinaisons de plusieurs commits) pour pouvoir merger votre branche.

Exemple de commit de merge

Nous reparlerons de tout cela dans un prochain article qui sera dédié aux commandes git avancées (il existe des techniques pour éviter cela si vous le voulez).

A noter que lorsqu’une branche est mergée, elle existe toujours et pointe toujours sur son dernier commit. Si vous voulez la supprimer, vous pouvez le faire avec git branch -D <nom>.

Pull

Git a été conçu pour que plusieurs personnes puissent travailler sur le même projet.

La commande git pull <remote> <branche> permet de récupérer l’état d’un dépôt distant sur votre machine local. La commande git pull est en fait la combinaison de deux actions:

  • Récupérer les modifications de votre remote (origin par exemple). Il est d’ailleurs possible d’exécuter seulement cette action avec la commande git fetch <remote>, donc git fetch origin dans notre cas.

  • Lancer un git merge pour fusionner les modifications appliquées sur votre remote avec votre travail local. Il existe là aussi plusieurs stratégies pour fusionner votre remote et votre travail local, et nous en reparlerons dans l’article suivant.

Conflits

Lorsque vous récupérez des modifications d’un dépôt distant, vous pouvez avoir des conflits. Imaginons que vous modifiez un fichier, puis créez un commit. Si quelqu’un a modifié la même partie de ce fichier sur votre dépôt distant, git pull produira un conflit:

$ git pull
Rembobinage préalable de head pour pouvoir rejouer votre travail par-dessus...
Application de  foo
Utilisation de l'information de l'index pour reconstruire un arbre de base...
Retour à un patch de la base et fusion à 3 points...
Fusion automatique de conflict
CONFLIT (ajout/ajout) : Conflit de fusion dans conflict
error: Échec d intégration des modifications.
le patch a échoué à 0001 foo
astuce: Utilisez 'git am --show-current-patch' pour visualiser le patch en échec

Résolvez tous les conflits manuellement, marquez-les comme résolus avec
"git add/rm <fichier en conflit>", puis lancez "git rebase --continue".
Si vous préférez sauter ce commit, lancez "git rebase --skip". Pour arrêter
et revenir à l état antérieur à la commande, lancez "git rebase --abort".

Le contenu du fichier conflict est dans mon cas:

<<<<<<< HEAD
hello
=======
conflict
>>>>>>> foo

On voit que quelqu’un a écrit "hello" dans ce fichier, alors que moi j’ai écrit "conflict". A moi de corriger le fichier (par exemple en ne laissant que "conflict" dedans) pour corriger le conflit.

Je peux ensuite utiliser git rebase --continue pour indiquer que le conflit est résolu (j’explique la commande rebase dans l’article suivant).

Il existe différentes façons de gérer les modifications apportées par git pull (merge ou rebase), ce qui influe sur la gestion des conflits. J’utilise personnellement la méthode du rebase en indiquant dans le fichier de configuration global de git (~/.gitconfig):

[pull]
  rebase = true

Lisez l’article suivant pour mieux comprendre rebase ;)

Tags

Un tag est seulement une étiquette ajoutée à un commit pour qu’il puisse être référencé de façon plus simple par un humain.

La commande git tag <nom> permet de créer un tag.

Il est également possible d’utiliser git tag -a <nom> pour créer un tag avec plus d’informations, comme son auteur ou un message de commit.

Il est possible, comme pour une branche ou pour un hash de commit, de se déplacer sur le commit référencé par un tag en utilisant git checkout <tag>. Cela est pratique si vous voulez vérifier l’état de votre travail sur un tag spécifique.

Fichier .gitignore

Vous voudrez parfois que certains fichiers ou dossiers ne soient pas visibles par Git, c’est à dire qu’ils ne puissent jamais être ajoutés à l’index et donc commités.

Cela est contrôlé par le fichier .gitignore, qu’il faut créer à la racine de votre projet. Si vous ajoutez par exemple la valeur my-file dans ce fichier, les fichiers nommés my-file présents dans votre projet seront invisibles pour Git.

Il existe d’autres "patterns" pour ignorer des répertoire, ou bien faire de la négation. Vous pouvez retrouver plus de détails sur ces options dans la documentation de Git.

Cette fonctionnalité est par exemple utile si vous avez besoin d’avoir des fichiers de configurations spécifiques, ou des mots de passes…​ à configurer dans votre projet local.

Conclusion et suite

Nous avons vu ici rapidement comment fonctionne Git. Dans l’article suivant, j’expliquerai des notions avancées et comment se sortir de situations difficiles.

je pense que la chose la plus importante à comprendre dans Git est le fait que l’on travaille avec des commits, et que nous avons des moyens de référencer ces commits (branches, tags…​).

Nous verrons dans l’article suivant qu’il est possible de jouer avec ces commits et ses références pour faire des choses intéressantes dessus.

Tags: programming
Top of page