Module 04 · Git & GitHub · Chapitre 1 · strict

1. Git - strict

Version fidèle au support fourni : formulations, exemples, commandes et exercices conservés. La présentation et la numérotation sont adaptées au wiki.

1.1 Introduction

Git est le système de gestion de versions (_Version Control System_ ou VCS) le plus populaire. Il a été développé par Linus Torvalds, créateur du noyau Linux. Il offre un type de gestion de versions très différent, car il s'agit d'un système de gestion de versions distribué. Avec un système de gestion de versions distribué, il n'y a pas une base de code centralisée d'où tirer le code. Vous observerez cela plus en détail avec les dépôts locaux et distants (grâce à GitHub).

D'autres systèmes de gestion de versions, tels que SVN et CVS, reposent sur un système centralisé, ce qui signifie qu'une seule copie maîtresse du logiciel est utilisée. Ces systèmes nécessitent donc une connectivité réseau pour toute action, au contraire de git qui va utiliser le réseau seulement lors d'une mise à jour entre le dépôt local et le dépôt distant.

C'est un gestionnaire de version qui est utilisé aussi bien pour des projets publics que des projets privés. Initialement, git permet de versionner des fichiers de code (python, C, C++...), mais il peut également servir pour tout type de fichier (txt, md, csv...).

L'intérêt de la gestion de versions (ou _versioning_) est de pouvoir revenir en arrière si besoin, et la capacité de voir qui a modifié quoi, à quel moment, ce qui apportera une meilleure gestion des participants du projet.

1.2 Premier pas

1.2.1 Installation

Git est installé par défaut dans de nombreuses distributions Linux. Il est notamment installé sur votre machine virtuelle DataScientest.

Si vous souhaitez l'installer sur votre ordinateur personnel, vous pouvez suivre les instructions ci-dessous.

Linux (Debian / Ubuntu)

Pour installer git, il suffit d'utiliser le gestionnaire de paquets apt:

sudo apt install git-all

macOS

Il existe plusieurs manières d'installer Git sur macOS.

Vous pouvez choisir le mode d'installation qui vous correspond parmi ceux listés sur le site officiel de git

Windows

Téléchargez l'assistant d'installation depuis le site officiel de git, exécutez le et suivez les instructions.

1.2.2 Initialisation

Nous allons à présent voir les commandes principales de git et nous rentrerons au fil du cours dans les détails de celles-ci.

Il faut avant tout créer un répertoire de travail. Une fois cette étape faite, il suffit de rentrer dans ce répertoire et d'utiliser la commande git init pour l'initialisation du dépôt en local.

mkdir repo
cd repo
git init

À la suite de cette commande, git crée un répertoire caché nommé .git . Pour rappel vous pouvez lister tous les éléments (y compris les éléments cachés) avec la commande ls -a. Vous pouvez rentrer dans le répertoire et faire un ls.

ls -a
cd .git
ls

C'est dans ce répertoire que vont être stockées toutes les données relatives à la création et modification d'éléments dans votre répertoire de travail. Ne prêtez pas forcément attention à ce qu'il y a dans ce répertoire, comprenez qu'il permet le bon fonctionnement de git.

1.2.3 Ajouter des documents

Lorsque l'on crée un fichier, pour que git prenne en compte l'ajout de ce fichier dans le dépôt il faut utiliser la commande git add nom_du_fichier

La commande d'ajout peut permettre d'ajouter plusieurs documents en même temps, voire de prendre en compte toutes les modifications qui ont été faites dans le répertoire.
C'est possible en utilisant "git add .".

Il faut néanmoins savoir que c'est une pratique à éviter. En effet le but va être d'historiser au mieux vos ajouts/modifications. Pour qu'en cas de besoin il soit plus simple dans le futur de comprendre ce qui a été fait dans votre répertoire.
Plus tôt vous prendrez de bons réflexes avec git, mieux ce sera pour vos futures missions pour lesquelles vous utiliserez git.

1.2.4 Faire un commit

On a mentionné plus haut le fait d'historiser vos ajouts/modifications de documents. Pour accomplir cette tâche, à la suite d'un ajout (git add), on doit faire un _commit_ c'est-à-dire enregistrer l'ajout ou la modification du document dans l'historique des modifications. On peut ajouter un message à nos _commits_ pour garder une trace commentée de toutes les modifications. On va donc faire un commit avec un commentaire intéressant et pertinent pour les utilisateurs grâce à la commande git commit -m "le commentaire".

Exemple de commentaires courts et explicites


git commit -m "ajout du fichier nom_du_fichier"
git commit -m "modification de la fonction nom_de_la_fonction"
git commit -m "ajout de documentation"

1.2.5 Supprimer des documents

Lorsque l'on souhaite supprimer un fichier, pour que git prenne en compte la suppression de ce fichier dans le dépôt il faut utiliser la commande git rm nom_du_fichier. Comme pour l'ajout ou la modification d'un document avec git add, il faudra également enregistrer la suppression avec un _commit_.


git rm  "mon_fichier.txt"
git commit -m "suppression du fichier mon_fichier.txt"

1.2.6 Connaître le statut de son dépôt local

À tout moment, il est possible de connaître l'état du dépôt: grâce à la commande git status, git nous guide également sur les actions que l'on doit faire. Cette commande est extrêmement importante, il est conseillé pour bien prendre en main l'outil de toujours faire un git status avant et après chaque commande pour voir l'évolution de nos documents.

Lors de l'exécution de git status, si rien n'est à faire, on devrait avoir l'affichage suivant.

On branch master

No commits yet

nothing to commit (create/copy files and use "git add" to track)

On comprend donc les informations qui vont nous être fournies à savoir: Sur quelle branche sommes nous (notion qui sera vue plus tard); s'il y a des fichiers à ajouter; s'il y a des fichiers à commiter.

1.2.7 Exemple

Nous allons à présent faire un exemple pour mettre en application ces commandes de bases avant de passer aux fonctionnalités avancées de git. Pour cela, il faut se mettre dans le dossier repo.

Créez un fichier doc.txt dans votre répertoire

Ajoutez du texte dans ce fichier (ex:"Mon premier fichier dans mon répertoire git")

Observer le statut du dépôt

Ajoutez doc.txt dans le git

Faites le commit pour l'ajout avec le message "ajout du fichier de documentation"

touch doc.txt
echo 'Mon premier fichier dans mon répertoire git' >> doc.txt
git status
git add doc.txt
git status
git commit -m "ajout du fichier de documentation"
git status

Vous savez à présent utiliser les commandes principales de git. Maintenant nous allons voir les fonctionnalités avancées afin de gérer au mieux les historiques de versions.

1.3 Configuration

Il est possible d'observer les variables de configuration de git à l'aide de la commande suivante.

git config --list

C'est ici que les informations concernant le fait qu'il y ait un dépôt distant ou non sont stockées. On peut également rajouter des informations concernant l'utilisateur. Par exemple, lors d'un commit il est important d'avoir les coordonnées (nom, email) de la personne qui a fait les modifications. Vous pouvez changer ces informations grâce à la commande suivante.

git config --global user.name "Daniel"
git config --global user.email "daniel@datascientest.com"

1.4 Log

Pouvoir observer les logs est essentiel pour un outil de gestion de versions. C'est une fonctionnalité très utilisée, notamment pour savoir quel utilisateur a fait quelle modification (et à quel moment). Cela permet de gérer les versions du répertoire de travail, d'avoir un vrai suivi des branches et de l'évolution des fichiers.

Faites une modification dans le fichier doc.txt, puis faites l'ajout et le commit.

echo 'nouvelle ligne' >> doc.txt
git status
git add doc.txt
git status
git commit -m "ajout de ligne"
git status

On va donc maintenant aller voir les logs ! Exécuter la commande git log et observer la sortie. Tout d'abord on peut remarquer entre nos deux commits que l'auteur a changé car l'on a modifié la configuration. Il existe des options de git log pour avoir un affichage avec une sortie plus élégante ou des informations en plus:

  • --oneline: Chaque commit est représenté sur une ligne.
  • --graph: Représente la sortie sous la forme d'un graphe (utile lorsqu'il y a plusieurs branches).
  • --name-status: Rajoute comme information le type d'action réalisée (A pour un ajout, M pour une modification)

Il en existe plein d'autres. Pour aller plus loin vous pouvez lire la documentation (commande man git-log) pour voir toutes les options disponibles, vous pouvez même combiner ces options! Voici des exemples avec les options combinées.

git log
git log --oneline
git log --oneline --graph
git log --oneline --graph --name-status #celle ci est un bon combo pour débuter avec git

1.5 Différence entre deux commit

Il se peut, lors du développement d'une application, qu'il y ait un incident en production ou dans un fichier que l'on a réalisé. Dans ce genre de situation, on veut rapidement voir les différences entre la version d'avant et d'après afin de voir les modifications ; pour cela, on utilise la commande git diff ou l'on passe en paramètre les numéros des deux commits à inspecter.

git diff numero_de_commit1 numero_de_commit2

Observer la différence entre vos deux commits. (des - et des + indiquent les ajouts et suppressions de ligne)

1.5.1 Git Blame

Une fonctionnalité intéressante de git est de pouvoir afficher les informations d'un document ligne par ligne, ainsi que l'auteur, la date du commit lié au changement d'état du document. On peut pour cela utiliser git blame nom_du_fichier.

1.6 Retour en arrière

Après avoir observé les différences entre les commits, on peut en cas d'incident vouloir revenir à une version précédente du code. Pour cela il existe plusieurs possibilités, les plus utilisées étant git revert et git reset.

git revert

La commande git revert permet de créer un nouveau commit qui annule un commit précédent, en appliquant la transformation inverse.

Par exemple, si le commit qui porte le numéro 123 crée un nouveau fichier fichier_1.txt, le commit git revert 123 créera un nouveau commit qui supprime le fichier fichier_1.txt.

Pour revenir en arrière d'un commit il faut donc utiliser la commande :


git revert numero_du_dernier_commit

On peut aussi revenir en arrière de plusieurs commits en utilisant git revert pour annuler une plage de commits :


git revert HEAD~2..HEAD # revenir 2 commit en arrière

Cette commande créera deux nouveaux commits, qui annulent respectivement le dernier et l'avant-dernier commit.

git reset

À la différence de git revert, qui crée des nouveaux commits pour annuler des commits passés, git reset supprime purement et simplement des commits de l'historique.

Ainsi, si l'historique des commits est le suivant : commit1 -- commit2 -- commit3 -- commit4, pour revenir à l'état du deuxième commit, on pourra utiliser la commande suivante:


git reset commit2 # revenir à l'état du commit commit2

Ces commandes auront pour effet de supprimer les commits commit3 et commit4. L'historique après cette opération sera donc commit1 -- commit2.

En comparaison, si l'on avait utilisé la commande git revert HEAD~2..HEAD, le nouvel historique serait commit1 -- commit2 -- commit3 -- commit4 -- commit5 -- commit6, les commits commit5 et commit6 ayant respectivement pour effet d'annuler les commit4 et commit3.

Si l'on souhaite annuler le précédent commit sans effacer les modifications faites en local lors de celui-ci, on utilisera l'argument --soft:

git reset --soft HEAD~1
# L'argument HEAD~1 précise que l'on souhaite revenir au commit précédent
# l'actuel étant (désigné par HEAD)

Si l'on souhaite annuler le commit ainsi que les modifications faites durant celui-ci, on utilisera l'argument --hard:

git reset --hard HEAD~1
Attention : La commande git reset --hard est destructive : elle supprime définitivement les commits et les modifications locales. Elle ne doit être utilisée qu'avec précaution, et jamais sur des commits déjà poussés sur un dépôt distant partagé. Privilégiez git revert dans le doute.

Utiliser git revert est considéré comme plus "propre", car on garde une trace de tous les commits, et de leurs suppressions.

Créez plus de commits et appliquez ces différentes fonctions.

1.7 Branche

Depuis le début du cours, on parle de branche, c'est le moment de comprendre cette fonctionnalité indispensable lors du développement d'un projet. Lorsque vous créez une application, il est toujours souhaitable d'avoir une version stable de celle-ci, mais que se passe-t-il si vous souhaitez développer une nouvelle fonctionnalité ? Car vous ne voulez pas écraser la version stable, ou vous ne voulez pas avoir à faire des git revert sans arrêt en cas d'erreur. C'est là que l'intérêt des branches entre en jeu.

1.7.1 Théorie

Illustration du support Liora — chapitre 1, figure 1

Par défaut une seule branche existe lors de l'initialisation du dépôt, la branche master (celle-ci a été renommée en branche main sur GitHub). La bonne pratique est de laisser la version de votre projet la plus stable sur celle-ci.

Lors du développement d'une nouvelle fonctionnalité vous serez amenés à créer une nouvelle branche. Le fait de créer une nouvelle branche va nous permettre de travailler avec les fichiers du répertoire courant dans la nouvelle branche. Ainsi vous pourrez développer votre nouvelle fonctionnalité sur la nouvelle branche, sans oublier les bonnes pratiques apprises jusqu'à présent (git add, status, commit...). Quand vous êtes satisfaits du travail réalisé sur la branche, il est possible de fusionner la nouvelle branche avec la branche master.

1.7.2 Pratique

1.7.2.1 Création de branche

Pour créer une nouvelle branche, on exécute la commande suivante depuis la branche courante:

git branch nom-branche

Pour observer les branches qui sont à notre disposition on peut simplement faire un git branch.

1.7.2.2 Changement de branche

Pour se déplacer de branche en branche on exécute la commande suivante:

git checkout nom-branche
Depuis Git 2.23, la commande git switch nom-branche est l'alternative recommandée pour changer de branche. git checkout reste fonctionnel mais est considéré comme plus ancien.

1.7.2.3 Fusion de branche (_merge_)

Enfin pour fusionner deux branches, par exemple si on veut apporter les changements de la branche branche-source à la branche master, il faudra d'abord se situer dans la branche master avec un git checkout puis utiliser la commande git merge :

git checkout master
git merge branche-source

1.7.3 Exercice

Créez une nouvelle branche modif-doc

Déplacez-vous dans celle-ci

Faites des modifications dans un document (par exemple via nano)

Enregistrez ces modifications (add + commit)

Assurez-vous que les modifications sont bien prises en compte dans la branche (status + log)

Retournez dans la branche master et observez que les modifications ne sont pas présentes

Fusionnez les deux branches et observez maintenant que les modifications se trouvent bien dans master

git branch modif-doc
git branch # l'astérisque indique la branche courante (c'est-à-dire sur laquelle on se situe)
git checkout modif-doc
git branch
nano doc.txt
git status
git add doc.txt
git status
git commit -m "modif doc.txt branche modif-doc"
git status
git log --oneline --graph --name-status
cat doc.txt
git checkout master
git branch
cat doc.txt
git merge modif-doc
cat doc.txt
git log --oneline --graph --name-status

En résumé, les branches permettent une meilleure gestion des versions des fichiers. Elles permettent également de travailler plus facilement à plusieurs (chaque personne peut travailler sur une branche distincte).

1.8 Gestion des conflits

Dans chaque situation où plusieurs personnes travaillent sur le même fichier, le travail finit par se chevaucher. Il arrive également que deux développeurs modifient la même ligne de code de deux manières différentes. Dans ces cas, Git ne peut pas dire quelle version est correcte, seulement un des développeurs peut décider.

Si cela se produit, un développeur verra l'erreur suivante pendant une fusion git :

Auto-merging [filename1]
CONFLICT (content): Merge conflict in [filename1]
Automatic merge failed; fix conflicts and then commit the result.

La résolution des conflits de fusion peut prendre une minute ou plusieurs jours (s'il y a beaucoup de fichiers à corriger). Il est recommandé, et c'est une bonne pratique de codage, de synchroniser votre code plusieurs fois par jour en faisant des commits, des pushs, et des merges régulièrement.

1.8.1 Exercice

Nous allons à présent voir un exemple de conflit et sa résolution:

Créez un nouveau dossier _conflits_

Entrez dans le dossier _conflits_

Initialisez le dépôt git

Créez un fichier _merge.txt_ et insérez le texte première ligne

Faites l'ajout et le commit

mkdir conflits
cd conflits
git init .
echo "first line" > merge.txt
git add merge.txt
git commit -m "add merge.txt file"

Nous disposons à présent d'un nouveau dépôt avec une branche master et un fichier merge.txt avec du contenu. Nous allons maintenant créer une branche ou nous allons créer un conflit.

Créez une nouvelle branche update-merge-file

Modifiez le contenu du fichier merge.txt

Faites l'ajout et le commit

git checkout -b update-merge-file
echo "new version of first line" > merge.txt
git add merge.txt
git commit -am "updated merge.txt"

Retournez maintenant sur la branche _master_

Ajoutez une deuxième ligne dans le fichier _merge.txt_

Faites l'ajout et le commit

git checkout master
echo "second line" >> merge.txt
git add merge.txt
git commit -am "added second line in merge.txt"

Maintenant que nous avons deux versions différentes du même fichier à travers deux branches différentes, nous allons essayer de faire un merge entre ces deux branches.

Fusionnez update-merge-file dans master

git merge update-merge-file

Le message suivant devrait apparaître.


Auto-merging merge.txt
CONFLICT (content): Merge conflict in merge.txt
Automatic merge failed; fix conflicts and then commit the result.

Le message nous indique donc qu'il y a un conflit avec le fichier _merge.txt_. Un git status permet de comprendre que le fichier _merge.txt_ a donc été modifié afin de régler le conflit. Nous allons voir à présent le contenu dans le fichier:


cat merge.txt

Nous pouvons voir certains ajouts dans le fichier:

  • <<<<<<< HEAD
  • =======
  • >>>>>>> update-merge-file

Ces lignes permettent de montrer le conflit et de comprendre la différence entre les deux branches :
Tout le contenu entre la ligne <<<<<<< HEAD et ======= provient de la branche actuelle vers laquelle pointe la réf HEAD (en l'occurrence master).
Et donc, tout le contenu entre ======= et >>>>>>> update-merge-file provient de la branche de merge (update-merge-file).

Afin de régler le problème, seul le développeur sait quelle version garder. Ainsi, la meilleure méthode pour résoudre celui-ci est de modifier manuellement les fichiers en question pour garder les versions souhaitées.

Modifiez le fichier Faites l'ajout et le commit

nano merge.txt
git add merge.txt
git commit -am "conflit résolu et merge des deux branches"

1.9 Tag

Une dernière notion importante à connaître lorsque l'on utilise git est le concept de _tag_. Les tags sont des références vers un point particulier de l'historique des modifications.

Un tag représente donc un _commit_ en particulier, alors qu'une branche représente une succession de _commits_. Les tags sont notamment utilisés pour indiquer un point important dans l'historique des modifications, par exemple des _commits_ correspondants à des versions déployées de l'application (c'est-à-dire version x.y.z, par exemple 1.0.1).

Pour créer un tag, il suffit d'exécuter la commande git tag suivi du nom du tag :

Créez un tag 1.0.0

git tag 1.0.0

1.10 Conclusion

Dans ce module nous avons vu les commandes principales pour pouvoir réaliser un projet avec git. Sachez que git est un outil complexe à prendre en main. C'est en pratiquant que vous allez assimiler au mieux les notions vues ici. Nous ne sommes pas rentrés dans les détails du travail en groupe avec git. Cette notion va être mise en avant dans le module GitHub, qui va permettre d'avoir un dépôt distant où plusieurs développeurs du même projet vont pouvoir travailler de manière collaborative.