Découverte :
Gérer les versions de vos fichiers : premiers pas avec git

logo Git

Logo de git (http://git-scm.com) Git Logo by Jason Long is licensed under the Creative Commons Attribution 3.0 Unported License

Git est un logiciel de contrôle de versions de fichiers. Il est distribué sous licence GNU GPLv2 et est disponible sur les principaux systèmes d'exploitation.

Cet article est le premier d'une série de deux. Nous allons voir ici (1) à quoi sert le contrôle de versions, (2) comment configurer git et (3) les bases de son utilisation.

Dans l'article suivant, nous verrons comment cloner un projet (par exemple pour travailler à plusieurs ou pour faire des sauvegardes) et comment synchroniser les différentes copies.

Contexte : le contrôle de versions

Pourquoi faire ?

En bioinformatique comme dans d'autres domaines, nous sommes tous concernés par des fichiers/programmes qui évoluent, que ce soient nos scripts ou nos fichiers textes personnels. Parmi les versions successives, on a typiquement besoin d'identifier la plus à jour ou celles qui correspondent à des versions majeures afin de les distinguer par rapport à toutes celles qui correspondent des changements mineurs.

 

De plus, l'enchaînement des versions n'est pas toujours strictement linéaire et il peut se produire des branchements où une version d'un document sert de point de départ à deux copies qui évoluent indépendamment l'une de l'autre.

Enfin, il est parfois nécessaire d'aller au delà des versions successives d'un seul document et d'administrer les versions successives de plusieurs documents qui correspondent ensemble à un projet.

Les problèmes précédents se compliquent lorsque l'on a besoin de maintenir à jour une copie de ces fichiers à différents endroits ou avec différentes personnes, et c'est encore pire si plusieurs de ces personnes peuvent faire des modifications et les partager à leur tour. Il faut alors gérer la synchronisation et les accès concurrents.

Logiciels de contrôle de version

Les logiciels de contrôle de versions servent précisément à gérer les versions successives d'un ensemble de documents, ainsi que leur partage, leur mise à jour entre plusieurs utilisateurs en gérant les conflits potentiels lorsque plusieurs personnes font des modifications concurrentes sur le même document. Les plus connus sont CVS, subversion (svn), bazaar (bzr), mercurial, ou git que nous allons présenter ici. On a souvent une vision un peu rébarbative des logiciels de contrôle de versions alors qu'ils sont en général simples à prendre en main et se révèlent très utiles. Cet article a justement pour but de vous aider à faire le premier pas.
Il existe par ailleurs une quantité impressionnante de didacticiels sur git. Souvent en anglais, mais promis, nous allons faire un effort ici.
CVS et SVN utilisent un modèle en étoile avec un répertoire maître sur lesquel se font toutes les mises à jour et sur lequel se synchronise chaque utilisateur. Inversement, mercurial, bazaar et git reposent sur une approche décentralisée : après duplication, la copie a le même statut que l'original (on utilise souvent la métaphore du clonage).
Initialement codé par Linus Torvalds pour gérer les versions de son célèbre noyau, git est aujourd'hui massivement utilisé dans le monde de l'open-source, notamment via des plateformes centralisées (bizarre, hein ?) telles que github ou bitbucket. CVS et SVN étaient la référence il y a quelques années, et même s'ils restent largement utilisés, git est aujourd'hui l'outil de gestion de versions le plus classique (on n'a pas dit le meilleur).
Bien sûr, comme nous ne sommes point intégristes, un petit encart montre l'équivalent sans git au début de chaque partie.
Pour un usage efficace d'un tutoriel, il est généralement conseillé de faire ce que personne ne fait jamais : reproduire les commandes et autres manipulations chez vous. (présence d'un adulte non nécessaire)
Et, n'oubliez jamais : un gestionnaire de versions se souvient de tout. NO EXCEPTION

Configurer git une bonne fois pour toutes

Sans git

Rien à faire pour cette étape. Cool, non ?

Avec git

Vous savez ce qu'on dit : un bon outil est difficile à prendre en main, mais une fois cela fait rien ne le remplace. C'est pareil pour git : 3 commandes suffisent à le configurer. Il faut d'abord ouvrir un terminal, et installer git :

Ensuite, avant que git ne vous le demande officiellement, vous devriez lancer ces deux commandes, très gitesques :

Pourquoi ?

Parce que c'est ainsi que git vous identifiera. Après avoir entré ces commandes, vous pourrez voir dans votre fichier ~/.gitconfig que les valeurs sont enregistrées ici… C'est en fait ici que git cherchera si, dans le projet dans lequel vous travaillez, aucune de ces informations n'est renseignée. Pour les trois de devant qui suivent : oui, cela permet d'avoir plusieurs identités.

Le cas Windows/OSX

Git est un programme suffisamment démocratisé pour se retrouver partout. Sous tout OS Unix-like (donc OSX aussi), git est utilisable dans la console, fût-elle bien cachée, et installable d'une manière ou d'une autre.

Pour Windows, on nous souffle en coulisse que Powershell gère l'affaire avec la même interface que les autres. Youpi !

Git pour remplir 2 Gb de RAM

Pour les plus allergiques à la ligne de commande, ou pour ceux qui n'intègrent pas cet outil à leur workflow, il existe une quantité importante d'interfaces graphiques qui encapsulent les commandes détaillées dans ce tuto.

Elles nécessitent toujours une compréhension de ce qu'est git et de ce qu'il fait (du moins cela ne peut que aider), ne vous attendez donc pas à vous économiser la totalité de ce tutoriel en vous contentant d'un clicodrôme (d'autant que certaines fonctionnalités avancées dont vous pourriez un jour avoir besoin pourraient nécessiter l'intervention en ligne de commande).

Créer un projet vide

Sans git

Un mkdir et pouf, c'est fini !

Avec git

Un "git init" et pouf, c'est fini aussi. On en profitera pour voir également "git status".

git init

Cette commande crée un dépôt (aussi appelé repository), c'est à dire un répertoire que git surveillera et gèrera, pendant que vous y ajouterez ou modifierez vos fichiers. Dans le jargon git, cela se traduit par la création d'un dossier caché nommé ".git", à l'endroit précis où vous vous trouvez quand vous lancez "git init". Ce répertoire .git contiendra toutes les informations sur le dépôt, ainsi que vos configurations locales. Certaines des (très) nombreuses options seront abordées dans cet article. Les autres, moins importantes, sont facilement trouvables sur le net, et, pour vos premières utilisations, il y a peu de chances que vous en ayez besoin :

Comme vous pouvez le constater, il y a déjà plein de trucs dans le .git alors qu'on n'a encore rien écrit… Rien d'anormal ; c'est comme quand vous venez d'installer un système : il y a déjà plein de bazar dans des répertoires inutiles tels que lib, bin ou dev, users, programs,… mais si vous tripotez trop à ce qui s'y trouve sans savoir ce que vous faites, eh bien… vous aurez quelques problèmes avec votre install. Git c'est pareil. Laissez-le gérer ses fichiers, il est meilleur que vous à ce niveau (pour les vôtres aussi d'ailleurs). Néanmoins, vous serez peut-être amenés à modifier un peu les fichiers ".git/config" et ".git/description" selon ce que vous voudrez, plus tard, quand cet article n'aura plus de secrets pour vous.

git status

Cette commande ne modifie rien, elle ne fait que parler. Et pourtant, vous l'adorerez : elle renseigne sur l'état actuel du dépôt. Voyez plutôt :

Ici, git nous indique clairement qu'on devrait se mettre au boulot. Il dit même comment (vous verrez, git est très malin à ce petit jeu), ce qui spoile un peu la prochaine partie de cet article.
Plus tard, cette commande nous resservira. C'est l'occasion de revenir sur le répertoire caché ".git" créé lors du "git init" : lorsque vous faite une commande git, par exemple "git status", git va chercher le premier répertoire ".git" qu'il trouvera en partant du répertoire courant puis en remontant successivement. De fait, lorsque vous serez la tête dans votre projet avec votre pwd quelque part dans la hiérarchie, git trouvera seul le dépôt, et trouvera les infos nécessaires dans le ".git". Par contre, lorsque vous n'êtes pas dans un dépôt et faites "git status", git va vous sermoner. La preuve : git est remonté jusqu'à la racine système sans trouver le moindre ".git".

Principes de base (pour travailler seul(e))

Sans git

Codez.
Si vous supprimez un fichier par erreur : perdez votre travail et priez pour foremost sans passer par la case départ.
Si vous voulez faire des sauvegardes de version, faites des dumps de dossiers avec des noms que vous ne comprendrez plus dans 2h, et que de toute façon vous ne réutiliserez jamais, sauf la semaine prochaine après le problème de sauvegarde ; mais de toute façon vu que vous aviez tout refactorisé, autant tout reprendre de zéro : effacez les dossiers de sauvegarde faits à la main, allez demander de l'aide sur stackoverflow. Expliquez à votre boss que vous n'auriez pas une semaine de retard si mv et rm n'était pas aussi proches syntaxiquement, apprenez l'existance d'un truc qui s'appelle gestionnaire de versions, prenez peur en lisant la page wikipédia, découvrez cet article. Expirez profondément, vous êtes sur la bonne voie et tout va déjà mieux.

Avec git

git status

Après la création du dépôt, "git status" vous disait que tout allait bien, et que la prochaine chose à faire est de créer des fichiers, puis d'utiliser "git add". Nous allons donc maintenant créer un fichier "firstFile.txt" et voir ce qui se passe.

Ici, le fichier est créé à l'aide de vi, mais, évidemment, vous pouvez utiliser n'importe quoi : git se contente de comparer des fichier. Vous pouvez copier-coller un fichier dans votre dépôt ou en créer un en modifiant votre disque dur avec un aimant ou un cure-dent, ce sera pareil : git n'a rien à voir avec un éditeur de fichier, et se contente de comparer des fichiers quand vous le lui demandez. (il existe en réalité une exception, lors de la gestion des conflits, qui sera vu plus tard)
Maintenant que nous avons créé le fichier, voici la réaction de git :

git add

Dans l'exemple précédent, on voit que créer le fichier firstFile.txt dans le dépôt ne suffit pas pour que git le prenne en charge automatiquement, et qu'il faut pour cela faire un "git add".

Cela peut sembler inutilement compliqué et malcommode, mais c'est nécessaire afin d'indiquer quels sont les fichiers intéressants. On verra dans la section sur .gitignore un peu plus loin comment traiter les fichiers pas intéressants.
Après avoir explicitement inclus firstFile.txt dans la liste des fichiers que git doit gérer, le dernier "git status" indique que le fichier est bien pris en compte. À ce stade, git sait qu'il doit surveiller ce fichier et il en a pris une empreinte de la version courante (un snapshot dans la documentation en anglais), qu'il stocke dans une zone temporaire appelée l'index. Si maintenant on modifie le fichier firstFile.txt, git sait qu'il doit le surveiller et indique que le snapshot qu'il a pris lors du "git add" n'est plus à jour. Il faut donc faire un nouveau "git add" pour réactualiser le snapshot. L'exemple suivant illustre ce principe : le premier "git status" reprend l'étape précédente et indique que tout va bien ; on modifie ensuite le fichier et le "git status" qui suit indique que git dispose bien d'un snapshot dans sa zone temporaire (c'est la ligne "new file: firstFile.txt" qui est similaire à ce qu'on avait avant), mais que ce snapshot n'est plus à jour car le fichier a été modifié depuis (ce sont les lignes à partir de "Changes not staged for commit:"). On fait de nouveau un "git add firstFile.txt" pour mettre à jour le snapshot et le troisième git status montre que tout va bien à nouveau et que les changements sont prêts pour un commit (cf. section suivante).

De même, si on crée de nouveaux fichiers (par exemple un readme et un fichier de licence) ou de nouveaux répertoires, il faut les ajouter avec "git add". Il est possible d'ajouter plusieurs fichiers avec un seul "git add". L'ajout d'un répertoire entraine l'ajout automatique de tous les fichiers qu'il contient ; les répertoires vides sont ignorés.

À propos de la discrimination des dossiers vides, car, diantre ! Cela peut être ennuyeux pour les dossiers de logs, et il ne s'agit pas d'un bug de fonctionnement :
dans la vraie vie réelle de l'internet, il existe plusieurs moyens de garder un dossier vide (comme ici ou ), et c'est a vous de choisir votre solution préférée (3615 ma life de lucas: j'ai une préférence pour le bricolage de .gitignore, puisqu'il permet de spécifier des patterns de fichier à ignorer, typiquement les fichiers avec une extension log, swp,…). L'usage du gitignore sera détaillé plus loin.

git commit (+ git status)

À ce stade, git surveille bien les fichiers et en prend des snapshots, mais l'index où ils sont stockés n'est qu'une zone temporaire. Lorsque les fichiers sont à jour, le "git status" indique qu'ils sont maintenant prêts à être archivés de façon définitive avec un "git commit". Typiquement, on travaille sur certains fichiers en faisant des modifications (éventuellement plusieurs, comme nous l'avons fait aux figures précédentes) jusqu'à réussir à ajouter une nouvelle fonctionnalité ou jusqu'à en avoir débuggué une. Le git commit permet alors de dire "Voilà, cette version des fichiers correspond à telle action". Cette version devient la nouvelle version à jour qui peut être partagée.
Remarquez que le seul changement de version "officielle" se fait d'un commit à l'autre, sans que personne ne voit la longue série de modifications plus ou moins habiles qui figurait dans les "git add" et qui vous a permis d'y parvenir.

Le paramètre -m permet d'indiquer un message décrivant (brièvement) la fonctionnalité que vous avez ajoutée ou celle que vous avez améliorée ou corrigée. Il faut que ce message soit concis et informatif (bon, ce n'est pas facile sur le premier commit). Si vous pensez qu'il faut donner des détails, commencez quand même par une phrase brève, laissez une ligne blanche, puis racontez ce que vous voulez.
Si vous ne donnez pas l'option m et le message de commit, git va appeler votre éditeur de texte par défaut (souvent nano si vous n'avez rien changé, définit par la variable d'environnement $EDITOR), ou un autre si vous avez utilisé la commande "git config --global core.editor "vim"" ou équivalent. Une fois démarré, l'éditeur de texte vous laissera écrire le message de commit.
Un parallèle avec l'industrie du bâtiment est possible : imaginez un commit comme une brique utilisée pour construire un mur. C'est l'ensemble des briques posées les unes sur les autres qui rendent le projet final. Plus tard, nous verrons qu'il est possible de cloner un mur en construction pour apposer ses propres briques, et de proposer ensuite une fusion des murs : le début du travail collaboratif.
Pour récapituler, il faut donc faire une combinaison d'une suite de "git add" pour ajouter les fichiers pertinents à l'index, et "git commit" pour valider les changements des fichiers de l'index.
Lorsque vous commencez à savoir ce que vous faites, un "git commit -a -m" combine les deux commandes précédentes en ajoutant les fichiers qui ont changé (mais pas les nouveaux) à l'index et en les validant. Il est donc aisé de commiter plusieurs fichiers simultanément. Néanmoins, cela va à l'encontre de la philosophie de git : un commit devrait toujours être une modification atomique, pas une pleine brouette de code désorganisée et obèse. Une exception notable est cependant lorsque vous créez un dépôt pour un projet, comme Linus avant tous, le premier commit (nommé souvent "initial commit") est généralement un gros dump de ce qui a été fait auparavant. Par exemple, cela permet simplement de suivre avec un échantillonnage élevé l'évolution d'un projet, et donc de contrôler facilement les ajouts de fonctionnalités, les versions,… Exactement ce pourquoi git existe. Et en plus c'est mieux pour faire de jolis graphiques !
Graphe d'activité du dépôt git du projet Julia

Graphe d'activité du dépôt git du projet Julia

Donc : préférez des commits atomiques et simples, touchant le minimum de fichiers à chaque fois, avec un message de commit qui répond au pourquoi, et non au comment dont la réponse est déjà dans le commit. Le dépôt du langage Julia présenté dans un autre article est un bon exemple : des milliers de commits, et même les tous premiers sont atomiques :

git log pour suivre l'historique des commit

La commande "git log" vous permet de lister l'auteur, la date et la description succinte (celle qui figurait après le -m) des "git commit" successifs.

La commande git log mériterait un tuto à elle toute seule. Dans un premier temps, la connaitre et l'utiliser sans arguments suffit largement.
Disons que les trois lignes précédentes seront votre premier TP, si par malheur vous ne reproduisez pas le tuto à la lecture.

git diff

Au cours du cycle typique "modification de fichier(s) <-> git add  <-> git commit", git permet plusieurs points de contrôle (le "git add" met à jour l'index temporaire et le "git commit" met à jour le dépôt à partir de l'index) et on peut avoir besoin d'analyser ce qui a changé de l'un à l'autre grâce à la commande "git diff".

  • Par défaut, "git diff" permet de comparer la version courante du fichier avec la dernière version prise en compte par l'index (donc ce qui a changé dans la version actuelle du fichier depuis le dernier "git add")
  • Par contre, "git diff --cached" permet de comparer la dernière version prise en compte par l'index avec l'état lors du dernier commit (donc ce qui va être modifié lors du prochain commit)

Il faut bien remarquer qu'on ne compare jamais la version actuelle du fichier avec celle du dernier commit, mais ce n'est pas très grave puisqu'il faut de toutes façons passer par un "git add" entre les deux. Pour bien détailler toutes ces étapes, on va faire une première modification dans un fichier, faire un "git add" pour mettre à jour l'index, puis faire une seconde modification. On voit que le "git diff" montre bien la version courante du fichier, mais ne met en valeur que la seconde modification, alors que "git diff --cached" ne montre et ne met en valeur que la première modification et ignore la seconde. Si ça vous semble normal, c'est que vous avez bien assimilé le principe de git ; sinon relisez les sections sur "git add" et "git commit".

Le dernier "git status" indique que firstFile.txt a été modifié depuis le dernier snapshot (ben oui, on vient d'y ajouter successivement deux lignes pour illustrer "git diff" et "git diff --cached"). On en profite pour mettre de l'ordre tout en récapitulant avec un "git add" pour mettre le snapshot à jour, puis un "git commit".

gitignore

Dans un projet, il y a des fichiers ou des répertoires qu'il n'est pas utile d'archiver ou de partager comme par exemple des fichiers intermédiaires de compilation, des fichiers de log, les fichiers .bak générés par certains éditeurs... Tant qu'on ne les ajoute pas grâce à un "git add" ce n'est pas bien grave, mais ils ont quand même l'inconvénient de polluer les "git status". De plus, on a vu précédemment que lors d'un "git add" sur un répertoire, git ajoute à l'index tous les fichiers du répertoire, y compris les fichiers indésirables.
En plus des fichiers inutiles, il est également bon d'éviter d'ajouter des fichiers binaires ou des fichiers compressés à un projet si ceux-ci peuvent être ammenés à changer. En effet, git est bien adapté aux fichiers textes mais pas trop aux autres types de fichiers (c'est logique, souvenez-vous du git diff de la section précédente). Normalement, à chaque modification, git ne stocke que ce qui a changé depuis la dernière version. Dans le cas des fichiers binaires, il est donc obligé de stocker la version complète du fichier à chaque modification, ce qui conduit rapidement à une augmentation importante de la taille du dépôt.
Il est possible d'indiquer à git qu'il doit ignorer certains fichiers (en donnant leur nom), ou certains types de fichiers (à l'aide d'expressions régulières). Pour cela, créez un fichier nommé ".gitignore" à la racine du projet (dans le répertoire où il y a aussi le répertoire ".git" généré lors du "git init"). Dans le fichier ".gitignore", mettez un nom de fichier ou une expression régulière par ligne.

Le dernier "git status" indique deux choses :
  • les fichiers se terminant en ".bak" et le répertoire "log" existent bien mais sont ignorés par git conformément au contenu du fichier ".gitignore"
  • le fichier ".gitignore" est traité par git comme un fichier normal (on va développer ce point dans le prochain paragraphe) et il nous faut donc faire un "git add" afin que git l'ajoute au dépôt, surveille ses modifications et le partage éventuellement avec d'autres utilisateurs.

Il faut bien comprendre que le contenu du fichier ".gitignore" est lu par git et qu'il indique les fichiers dont git ne tient pas compte lors de la mise à jour de l'index. Si vous avez l'esprit taquin, vous pouvez évidemment mentionner ".gitignore" dans votre ".gitignore". Git continuera bien à en lire le contenu, mais n'ajoutera simplement pas votre ".gitignore" à l'index.
Ce n'est sans doute pas une bonne idée si plusieurs personnes collaborent au projet. En effet, vos partenaires ne récupèreront donc pas votre ".gitignore" avec les autres fichiers du projet. Ils risquent alors d'ajouter des fichiers indésirables lorsqu'ils feront un "git commit" (mais grâce à la nature décentralisée de git, vous aurez quand même le loisir de rejeter leur commit, il vous faudra juste refaire le ménage à la main).
On a évidemment tendance à conserver les mêmes .gitignore d'un projet à l'autre, donc c'est un bon investissement. Il existe également des générateurs de .gitignore qui vous aident dans cette tâche.
Une application très parlante du gitignore dans tout projet Python est d'ignorer l'ensemble des fichiers issus de la compilation :

On peut également utiliser le gitignore pour conserver un dossier vide, comme expliqué dans la partie dédiée à git add, tout en évitant de tracker l'ensemble des fichiers de logs.

Note importante : avoir un gitignore n'est pas obligatoire. Un gitignore n'affecte que le dossier où il se trouve ainsi que ses sous-dossiers. Vous pouvez avoir plusieurs gitignore.

 

Conclusion

Les outils de gestion de versions comme git sont bien plus simples à utiliser qu'on ne le pense (du moins pour une utilisation de base).

Nous avons vu comment créer un dépôt, sélectionner les fichiers et les répertoires que l'on veut prendre en compte et enregistrer leurs versions successives. Dans le prochain article, nous verrons comment utiliser git dans un contexte collaboratif, détecter et résoudre les conflits que cela peut entrainer, créer des branches et indiquer les étapes qui correspondent à des versions.

L'excellent site first aid git recense les questions les plus fréquentes sur git (et leurs réponses)

Remerciements

Merci à Hautbit, NiGoPol et Slybzh pour les commentaires et discussions lors de l'édition de cet article.

À propos de cet article

Cet article et le suivant ont étés adaptés à partir d'un cours donné par Lucas Bourneuf.

Il a été rédigé par Lucas Bourneuf et Olivier Dameron.

4 commentaires sur “Gérer les versions de vos fichiers : premiers pas avec git

  1. On aurait pu aussi parler de forge logiciel (site proposant les mêmes fonctionnalités que github) libre, je citerais mais ce ne sont pas les seuls :

    * gitlab http://gitlab.org/ une instance tenue par framasoft : https://git.framasoft.org/
    * gogs http://gogs.io/ la démo : https://try.gogs.io/

    Il y en a bien d'autres et beaucoup que j'ai vu passé mais ne retrouve plus le nom, l'offre de forge logiciel est assez impressionnante.

    Pour ceux qui voudraient retrouver l'aspect décentralisé de git je vous conseille d'aller voir du coté de gitchan http://gitchain.org/

  2. Très bon tuto. Un petit firstFile.py à un moment sème le doute 😉

Laisser un commentaire