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 licen­sed under the Crea­tive Com­mons Attri­bu­tion 3.0 Unpor­ted License

Git est un logi­ciel de contrôle de ver­sions de fichiers. Il est dis­tri­bué sous licence GNU GPLv2 et est dis­po­nible sur les prin­ci­paux sys­tèmes d'exploitation.

Cet article est le pre­mier d'une série de deux. Nous allons voir ici (1) à quoi sert le contrôle de ver­sions, (2) com­ment confi­gu­rer git et (3) les bases de son uti­li­sa­tion.

Dans l'article sui­vant, nous ver­rons com­ment clo­ner un pro­jet (par exemple pour tra­vailler à plu­sieurs ou pour faire des sau­ve­gardes) et com­ment syn­chro­ni­ser les dif­fé­rentes copies.

Contexte : le contrôle de versions

Pourquoi faire ?

En bio­in­for­ma­tique comme dans d'autres domaines, nous sommes tous concer­nés par des fichiers/​programmes qui évo­luent, que ce soient nos scripts ou nos fichiers textes per­son­nels. Par­mi les ver­sions suc­ces­sives, on a typi­que­ment besoin d'iden­ti­fier la plus à jour ou celles qui cor­res­pondent à des ver­sions majeures afin de les dis­tin­guer par rap­port à toutes celles qui cor­res­pondent des chan­ge­ments mineurs.

 

De plus, l'enchaî­ne­ment des ver­sions n'est pas tou­jours stric­te­ment linéaire et il peut se pro­duire des bran­che­ments où une ver­sion d'un docu­ment sert de point de départ à deux copies qui évo­luent indé­pen­dam­ment l'une de l'autre.

Enfin, il est par­fois néces­saire d'aller au delà des ver­sions suc­ces­sives d'un seul docu­ment et d'admi­nis­trer les ver­sions suc­ces­sives de plu­sieurs docu­ments qui cor­res­pondent ensemble à un pro­jet.

Les pro­blèmes pré­cé­dents se com­pliquent lorsque l'on a besoin de main­te­nir à jour une copie de ces fichiers à dif­fé­rents endroits ou avec dif­fé­rentes per­sonnes, et c'est encore pire si plu­sieurs de ces per­sonnes peuvent faire des modi­fi­ca­tions et les par­ta­ger à leur tour. Il faut alors gérer la syn­chro­ni­sa­tion et les accès concur­rents.

Logiciels de contrôle de version

Les logi­ciels de contrôle de ver­sions servent pré­ci­sé­ment à gérer les ver­sions suc­ces­sives d'un ensemble de docu­ments, ain­si que leur par­tage, leur mise à jour entre plu­sieurs uti­li­sa­teurs en gérant les conflits poten­tiels lorsque plu­sieurs per­sonnes font des modi­fi­ca­tions concur­rentes sur le même docu­ment. Les plus connus sont CVS, sub­ver­sion (svn), bazaar (bzr), mer­cu­rial, ou git que nous allons pré­sen­ter ici. On a sou­vent une vision un peu rébar­ba­tive des logi­ciels de contrôle de ver­sions alors qu'ils sont en géné­ral simples à prendre en main et se révèlent très utiles. Cet article a jus­te­ment pour but de vous aider à faire le pre­mier pas.
Il existe par ailleurs une quan­ti­té impres­sion­nante de didac­ti­ciels sur git. Sou­vent en anglais, mais pro­mis, nous allons faire un effort ici.
CVS et SVN uti­lisent un modèle en étoile avec un réper­toire maître sur les­quel se font toutes les mises à jour et sur lequel se syn­chro­nise chaque uti­li­sa­teur. Inver­se­ment, mer­cu­rial, bazaar et git reposent sur une approche décen­tra­li­sée : après dupli­ca­tion, la copie a le même sta­tut que l'original (on uti­lise sou­vent la méta­phore du clo­nage).
Ini­tia­le­ment codé par Linus Tor­valds pour gérer les ver­sions de son célèbre noyau, git est aujourd'hui mas­si­ve­ment uti­li­sé dans le monde de l'open-source, notam­ment via des pla­te­formes cen­tra­li­sées (bizarre, hein ?) telles que github ou bit­bu­cket. CVS et SVN étaient la réfé­rence il y a quelques années, et même s'ils res­tent lar­ge­ment uti­li­sés, git est aujourd'hui l'outil de ges­tion de ver­sions le plus clas­sique (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 par­tie.
Pour un usage effi­cace d'un tuto­riel, il est géné­ra­le­ment conseillé de faire ce que per­sonne ne fait jamais : repro­duire les com­mandes et autres mani­pu­la­tions chez vous. (pré­sence d'un adulte non néces­saire)
Et, n'oubliez jamais : un ges­tion­naire de ver­sions se sou­vient 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 dif­fi­cile à prendre en main, mais une fois cela fait rien ne le rem­place. C'est pareil pour git : 3 com­mandes suf­fisent à le confi­gu­rer. Il faut d'abord ouvrir un ter­mi­nal, et ins­tal­ler git :

 # Vous pouvez utiliser les paquets de votre distribution, ou à défaut télécharger git depuis http://git-scm.com.
# Ici on se simplifie la vie et on prend la première option.
# Adaptez selon votre distribution.
sudo apt-get install git # debian, ubuntu et consorts
sudo pacman -S git # archlinux
# pour les autres, soit vous savez, soit vous êtes sur mac ou windows…
Ensuite, avant que git ne vous le demande officiellement, vous devriez lancer ces deux commandes, très gitesques :
git config --global user.name "Gérard Menvuça"
git config --global user.email "super.gege@wanadold.fr"

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 aus­si. On en pro­fi­te­ra pour voir éga­le­ment "git status".

git init

Cette com­mande crée un dépôt (aus­si appe­lé repo­si­to­ry), c'est à dire un réper­toire que git sur­veille­ra et gère­ra, pen­dant que vous y ajou­te­rez ou modi­fie­rez vos fichiers. Dans le jar­gon git, cela se tra­duit par la créa­tion d'un dos­sier caché nom­mé ".git", à l'endroit pré­cis où vous vous trou­vez quand vous lan­cez "git init". Ce réper­toire .git contien­dra toutes les infor­ma­tions sur le dépôt, ain­si que vos confi­gu­ra­tions locales. Cer­taines des (très) nom­breuses options seront abor­dées dans cet article. Les autres, moins impor­tantes, sont faci­le­ment trou­vables sur le net, et, pour vos pre­mières uti­li­sa­tions, il y a peu de chances que vous en ayez besoin :
 $ mkdir /some/path/to/your/project # création du répertoire
$ cd /some/path/to/your/project # on va dedans
$ ls -a # le répertoire est bien vide
. ..
$ git init # bon, ben, créons un dépôt git vide initialisé dans /some/path/to/your/project/.git/
$ ls -a # la commande "git init" crée un répertoire caché ".git"
. .. .git
$ ls .git # on jette un oeil dans le répertoire
branches config description HEAD hooks info objects refs
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 :
$ git status
On branch master
Initial commit
nothing to commit (create/copy files and use "git add" to track)
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.
$ vi firstFile.txt
$ cat firstFile.txt
This is the first line of my first file...
... and this is the second line

$

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 status
On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
firstFile.txt
nothing added to commit but untracked files present (use "git add" to track)
$

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".
$ git add firstFile.txt
$ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: firstFile.txt
$
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).
$ cat firstFile.txt
This is the first line of my first file...
... and this is the second line

$ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: firstFile.txt
$ vi firstFile.txt
$ cat firstFile.txt
This is the first line of my first file...
... and this is the second line
Now we add another line

$ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: firstFile.txt
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: firstFile.txt
$ git add firstFile.txt
$ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: firstFile.txt

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.
$ ls
firstFile.txt
$ vi README.txt
$ vi licence.txt
$ mkdir doc
$ vi doc/documentation.txt
$ vi doc/otherDocumentation.txt
$ find . -name .git -a -type d -prune -o -print
.
./README.txt
./doc
./doc/documentation.txt
./doc/otherDocumentation.txt
./firstFile.txt
./licence.txt
$ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: firstFile.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
README.txt
doc/
licence.txt
$ git add README.txt licence.txt doc
$ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: README.txt
new file: doc/documentation.txt
new file: doc/otherDocumentation.txt
new file: firstFile.txt
new file: licence.txt

À 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.
$ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: README.txt
new file: doc/documentation.txt
new file: doc/otherDocumentation.txt
new file: firstFile.txt
new file: licence.txt
$ git commit -m "First commit"
[master (root-commit) e26b158] First commit
5 files changed, 10 insertions(+)
create mode 100644 README.txt
create mode 100644 doc/documentation.txt
create mode 100644 doc/otherDocumentation.txt
create mode 100644 firstFile.txt
create mode 100644 licence.txt
$ git status
On branch master
nothing to commit, working directory clean
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 clone https://github.com/JuliaLang/julia
cd julia
git log --reverse # afficher les commits du plus ancien au plus récent

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".

$ cat firstFile.txt
This is the first line of my first file...
... and this is the second line
Now we add another line

$ git status
On branch master
nothing to commit, working directory clean
$ echo "Adding a fourth line" >> firstFile.txt
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: firstFile.txt
no changes added to commit (use "git add" and/or "git commit -a")
$ git diff
diff --git a/firstFile.txt b/firstFile.txt
index 9a4e002..d5078b7 100644
--- a/firstFile.txt
+++ b/firstFile.txt
@@ -3,3 +3,4 @@ This is the first line of my first file...
Now we add another line

+Adding a fourth line
$ git add firstFile.txt
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: firstFile.txt
$ git diff
$ git diff --cached
diff --git a/firstFile.txt b/firstFile.txt
index 9a4e002..d5078b7 100644
--- a/firstFile.txt
+++ b/firstFile.txt
@@ -3,3 +3,4 @@ This is the first line of my first file...
Now we add another line

+Adding a fourth line
$ echo "Adding a fifth line" >> firstFile.txt
$ git diff
diff --git a/firstFile.txt b/firstFile.txt
index d5078b7..ba75664 100644
--- a/firstFile.txt
+++ b/firstFile.txt
@@ -4,3 +4,4 @@ Now we add another line

Adding a fourth line
+Adding a fifth line
$ git diff --cached
diff --git a/firstFile.txt b/firstFile.txt
index 9a4e002..d5078b7 100644
--- a/firstFile.txt
+++ b/firstFile.txt
@@ -3,3 +3,4 @@ This is the first line of my first file...
Now we add another line

+Adding a fourth line
$ git commit -m "this commit only adds the fourth line; the fifth is ignored as it is not yet in the index"
[master 61b5f1d] this commit only adds the fourth line; the fifth is ignored as it is not yet in the index
1 file changed, 1 insertion(+)
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: firstFile.txt
no changes added to commit (use "git add" and/or "git commit -a")
$ git diff
diff --git a/firstFile.txt b/firstFile.txt
index d5078b7..ba75664 100644
--- a/firstFile.txt
+++ b/firstFile.txt
@@ -4,3 +4,4 @@ Now we add another line

Adding a fourth line
+Adding a fifth line
$ git diff --cached
$

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".

$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: firstFile.txt
no changes added to commit (use "git add" and/or "git commit -a")
$ git add firstFile.txt
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: firstFile.txt
$ git commit -m "added two more lines to explain how git diff works"
[master fcae267] added two more lines to explain how git diff works
1 file changed, 1 insertion(+)
$ git status
On branch master
nothing to commit, working directory clean
$

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.
$ git status
On branch master<br>nothing to commit, working directory clean
$ touch firstFile.txt.bak
$ mkdir log
$ touch log/example.log
$ touch log/example.log.gz
$ git status
On branch master<br>Untracked files:
(use "git add <file>..." to include in what will be committed)
firstFile.txt.bak
log/
nothing added to commit but untracked files present (use "git add" to track)
$ vi .gitignore
$ cat .gitignore
*.bak
log/
$ On branch master
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
nothing added to commit but untracked files present (use "git add" to track)
$ ls -a
. .. doc firstFile.txt firstFile.txt.bak .git .gitignore licence.txt log README.txt
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.
$ git add .gitignore
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: .gitignore
$ git commit -m "added a .gitignore file to ignore backup files and the log directory"
[master 7deabd7] added a .gitignore file to ignore backup files and the log directory
1 file changed, 2 insertions(+)
create mode 100644 .gitignore
$ git status
On branch master
nothing to commit, working directory clean<
$
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 :
*.pyc
__pycache__/
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.



Pour continuer la lecture :


Commentaires

4 réponses à “Gérer les versions de vos fichiers : premiers pas avec git”

  1. Avatar de Pierre Marijon
    Pierre Marijon

    On aurait pu aus­si par­ler de forge logi­ciel (site pro­po­sant les mêmes fonc­tion­na­li­tés que github) libre, je cite­rais mais ce ne sont pas les seuls :

    * git­lab http://​git​lab​.org/ une ins­tance tenue par fra­ma­soft : https://​git​.fra​ma​soft​.org/
    * gogs http://​gogs​.io/ la démo : https://​try​.gogs​.io/

    Il y en a bien d'autres et beau­coup que j'ai vu pas­sé mais ne retrouve plus le nom, l'offre de forge logi­ciel est assez impres­sion­nante.

    Pour ceux qui vou­draient retrou­ver l'aspect décen­tra­li­sé de git je vous conseille d'aller voir du coté de git­chan http://​git​chain​.org/

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

    1. Bien vu 😉
      C'est cor­ri­gé !

Laisser un commentaire