AXOPEN

Git : comprendre la gestion de versions

1 – Préambule

Cet article a pour but de donner une première approche de git (prononcé ‘guitte’ le plus souvent). Dans cette optique, nous présenterons le système de gestion de versions afin d’avoir une approche saine de git. Nous ne verrons pas exhaustivement toutes les fonctionnalités de git mais les plus essentielles. Si vous voulez connaître plus en profondeur les actions possibles avec git, la documentation officielle de git est très bien écrite et vous permettra de connaître les commandes en profondeur. Si cet article remplit son rôle, vous n’aurez plus qu’à vous référer à la documentation officielle pour effectuer des actions spécifiques et vous aurez une bonne compréhension du système dans son ensemble.

Notez que la plus grande difficulté dans l’utilisation de git est son jargon : beaucoup de gens se sentent perdus car les termes ne semblent pas toujours naturels. Ceux-ci sont pourtant essentiels car en effet git est un système complexe (mais pas difficile), en comprenant les termes vous appréhenderez aisément le système dans son ensemble.

2 – Git : pourquoi

Il est difficile de démontrer tous les bénéfices de git par rapport à ses concurrents avant d’expliquer plus en détails son fonctionnement, mais il est clairement important d’y répondre. Voici différents points forts de git :

  • L’espace disque utilisé par git est très faible comparé à ses concurrents
  • Le système décentralisé est très flexible et répond à beaucoup de problématiques non résolues par les autres systèmes centralisés (voir chapitre suivant)
  • Licence GNU : git appartient au domaine publique, il est aussi très « suivi » (mis à jour).
  • Git est de loin le système le plus populaire : tout outil de développement « digne de ce nom » l’intègre et il existe de nombreux services associés très populaires (Github, Gitlab, …)

3 – Git : la gestion de versions

3.1 – Introduction

Pour commencer parlons des gestionnaires de versions (ou VCS en anglais, pour Version Control System), l’idée semble simple au premier abord : avoir un système permettant d’enregistrer l’ensemble des fichiers d’un projet, comme des ‘back-up’ (fichiers de secours), que l’on pourrait sauvegarder les uns après les autres comme des versions de notre projet.

L’ensemble des sauvegardes d’un projet (dit versionné) est appelé dépôt (ou repository). Retenez bien le jargon car cela vous aidera afin de comprendre cet article mais aussi toute documentation que vous pourrez lire.

git_depot_projet

Nous allons voir comment ce processus devient plus fin et plus complexe en voulant un système performant et puissant.

Si vous imaginez utiliser un tel système de sauvegarde, ou si vous l’avez déjà fait, vous vous rendez compte que la grande majorité du temps les modifications ne touchent qu’une petite partie de votre projet. Ainsi les logiciels de gestion de versions ne vont pas sauvegarder tous les fichiers à chaque fois mais les différences entre la nouvelle version et son antécédent. Ainsi votre dépôt nécessitera un espace bien moindre sur votre disque.

Note : N’ayez pas peur de créer une nouvelle version de votre projet pour chaque petite modification apportée. L’espace utilisé sur votre disque est très minime. Une grande modification (touchant beaucoup de lignes/fichiers) sera bien moins lisible si quelqu’un doit revenir dessus.

Avec seulement ces premiers points, nous pouvons comprendre comment un logiciel de gestion de versions fonctionne. Il y a d’un côté le dépôt contenant l’ensemble des versions (en fait des sortes de patchs afin de passer d’une version à l’autre), et vous pouvez travailler sur vos fichiers de votre côté. Si vous appréhendez bien cette logique, vous comprenez que les logiciels de gestion de versions fonctionnent avec un côté serveur (gérant le dépôt, sur une machine distante de préférence), et un côté client (appelant le dépôt, sur votre ordinateur).

Vous allez ainsi utiliser votre logiciel de gestion de versions (côté client) avec principalement les actions suivantes :

  • Récupérer le(s) fichier(s) : checkout
  • Envoyer une nouvelle version : commit (normalement accompagné d’un message)
  • Récupérer la dernière version : update
  • Voir l’état de vos fichiers par rapport à la dernière version : status
  • Voir l’historique des versions : log
  • Comparer 2 versions : diff

Ces actions sont communes à la grande majorité des logiciels, git ajoutera quelques subtilités que nous verrons plus tard.

3.2 – Cycle de travail classique

Avant d’avancer plus spécifiquement sur git voyons un cycle de travail classique d’une personne sur un projet (avec un système de version simple).

Cycle de travail simple (1 utilisateur)

git_gestion_projet_simple

Dans cet exemple les choses sont très simples mais si vous travaillez en équipe, un problème va apparaître : si le même fichier est modifié par 2 personnes différentes, comment mettre à jour le dépôt? La solution est la fusion (ou merge en anglais), la première personne à envoyer (commit) sa version n’aura pas de soucis mais la deuxième devra d’abord récupérer cette nouvelle version et la fusionner avant de pouvoir ‘commit’.

Cycle de travail avec 2 utilisateurs : situation de conflit

git-gestion-projet-merge

Ce problème de fusion est récurrent et source de beaucoup de problèmes et d’incompréhension. Les fichiers à fusionner vont faire apparaître des balises spécifiques afin d’indiquer les morceaux à fusionner. Le plus simple, et le plus courant, est d’utiliser des programmes afin de gérer les fusions plus simplement (car les balises sont très difficilement lisibles pour les humains).

Ces 2 premiers exemples restent simples, vous allez peut être vouloir travailler sur des fonctionnalités dont vous n’êtes pas encore sûr qu’elles seront intégrées au projet final, ajouter des corrections sur des versions précédentes, ou travailler avec des équipes hiérarchisées. Avec un système simple comme présenté jusqu’à présent, ces problématiques sont loin d’être résolues. C’est ici que git nous montrera tout son potentiel.

3.3 – Git : un système décentralisé

Le système de gestion de versions décrit jusqu’ici représente une gestion de versions proche de Subversion (abrégé SVN, développé par Apache). SVN est(était?) très plus populaire, nous le prendrons comme représentant des anciens systèmes de gestion (comme CVS ou d’autres). La popularité de git est toujours croissante, bon nombre de particuliers et d’entreprises migrent vers git, beaucoup de sources considèrent désormais git comme étant très largement le plus utilisé (exemple de statistique : Google trend).

La grande différence de git : la décentralisation et les branches.

Ces fonctionnalités permettent de résoudre les problématiques décrites en fin du dernier chapitre.

Note : Il existe aussi Mercurial (GNU) comme système décentralisé.

3.3.1 – La décentralisation

La décentralisation est un concept assez simple, chaque client devient aussi un serveur indépendant. Votre dépôt habituel peut être considéré comme un simple client.

Ainsi, chaque client (ordinateur) possède son propre dépôt (repository), dit local. Un commit n’envoie donc plus sur un dépôt central mais sur le local.

Une nouvelle commande fait son apparition : push, celle-ci permet d’envoyer le(s) versions contenues dans le dépôt local vers un distant (dit remote). Vous pouvez voir cette opération comme une synchronisation (dans un sens seulement).

Aussi, l’équivalent de la commande update sera pull (en opposition à push) afin de récupérer les informations du dépôt distant dans votre dépôt local (les fichiers sont mis à jour par la même occasion).

Comprenez bien que, désormais, vous pouvez aussi avoir plusieurs dépôt distants, lors d’un push vous pouvez donner l’adresse du dépôt destinataire. Cependant, pour éviter de répéter toujours cette information, le plus courant est de définir votre dépôt ‘par défaut’ : votre upstream. Ne vous inquiétez pas trop sur ce point, si vous l’oubliez, git vous rappellera élégamment comment l’affecter si vous faite un simple push.

Afin de gérer ces dépôts distants, git possède plusieurs sous-commandes depuis la commande remote, parmi lesquels:

  • git remote : affiche la liste des dépôts enregistrés (simplement les nom)
  • git remote -v : affiche la liste des dépôts enregistrés (nom et adresse)
  • git remote add <nom du dépôt> <adresse du dépôt> : ajoute un nouveau dépôt
  • git remote remove <nom du dépôt> : supprime un dépôt

Par défaut le dépôt principal est souvent noté origin, donnant la commande :

git remote add origin <adresse du dépôt>

Note : si vous avez bien compris le système vous devriez mieux appréhender le fait qu’on ne commit pas directement vers un dépôt distant. En fait, on ne fait normalement aucune opération directement sur un dépôt distant, on effectue les opérations souhaitées sur son dépôt local puis on push.

Note : grâce à ce système vous pouvez donc faire des commit même si vous n’avez aucune connexion (internet/à votre dépôt distant).

3.3.2 – Les branches

Ce concept existait en fait sur les anciens systèmes aussi mais restait très rudimentaire. Une branche est une sorte de sous-dépôt, elle peut aussi commencer depuis une version particulière et se terminer.

Au sein d’un dépôt, vous allez donc pouvoir commit sur une branche en particulier, par défaut, la branche principale est master.

Afin de gérer les branches vous utiliserez le plus souvent les commandes suivantes :

  • git branch <nom de la branche> : créer une nouvelle branche
  • git checkout <nom de la branche> : change de branche (oui c’est comme récupérer un dépôt en fait)
  • git merge <nom de la branche> :  fusionne une branche vers la branche courante

Vous comprendrez que ce système de branche permet à plusieurs personnes de travailler sur le même projet sans créer de conflits. Une fois les travaux terminés, les branches peuvent être fusionnées par la suite. De même, vous pouvez imaginer un système de branches hiérarchisées. En fait, la souplesse induite par ce système permet beaucoup de possibilités et la difficulté principale sera pour vous de trouver une structure adaptée à votre projet et à votre équipe.

Note : le système de branche peut aussi vous permettre de faire des commits intermédiaires sur une branche spéciale si vous voulez avec des sauvegardes régulières puis fusionné sur une branche dont les commits correspondent plus au processus de développement plutôt qu’à des impératifs heuristiques (si vous en avez). En effet, il est préférable d’avoir des commits reflétant l’ajout de fonctionnalités/correctifs et non des commits partielles effectués par simple sécurité.

3.4 – Git : le staging

Si vous avez suivi l’article jusqu’ici, vous connaissez l’opération commit pour envoyer une nouvelle version. Cependant, il peut être très intéressant de vérifier le commit avant de l’envoyer ou de n’envoyer qu’une partie des fichiers.

Pour ceci git ajoute une nouvelle étape : le staging (ou encore l’index).

L’idée de base encore une fois est simple : vous annoncez d’abord les fichiers que vous voulez envoyer avant de faire votre commit. Il y a en effet un index (à l’image de l’index d’un livre) contenant les différentes entrées (ici des fichiers) pour le futur commit. Les commandes de git pour gérer cette phase vont jouer sur cet index :

  • git add <fichier/dossier> : Ajouter un fichier/dossier à l’index (possibilité de wildcard comme ‘*.html’)
  • git rm –cached <fichier/dossier> : Enlève un fichier/dossier à l’index (sans l’attribut –cached vous supprimer le fichier par la même occasion)
  • git status : Affiche la différence entre vos fichiers et la dernière version (de la branche actuelle). Cette opération, avec git, montre aussi les fichiers indexés (prévus pour le prochain commit) et ceux qui ne le sont pas.

Note : pour supprimer un fichier sur votre dépôt vous devez donc le supprimer normalement puis ajouter son entrée à l’index (ce processus peut sembler déroutant aux premiers abords puisque l’on utilise bien la commande git add, bien que nous voulons supprimer un fichier).

Note : vous pouvez inclure un fichier nommé ‘.gitignore’ dans votre projet (dans le dossier racine) afin que git ignore complètement certains fichiers (comme si ils étaient transparents). Dans ce fichier chaque ligne représentera un ‘pattern’ de fichier/dossier à ignorer.

Note : beaucoup de commandes peuvent être effectuées sur les fichiers indexés seulement, vous verrez alors les attributs –cached ou –staged. Ces 2 noms désignent bien la même chose (exemple : ‘git diff –cached’ et ‘git diff –staged’ pour voir les différences entre une version et les fichiers indexés).

4 – Git : exemple d’utilisation

4.1 – Installation

Cet article (déjà trop long) ne traitera malheureusement pas de l’installation. Rassurez-vous le site officiel est très bien présenté et vous ne devriez rencontrer aucune difficulté sur cette étape.

4.2 – Création du serveur

Beaucoup de personnes utilisent Github ou Gitlab, ces services permettent de gérer des dépôts distants, avec des services d’authentification, de tickets (pull request, issues…). Cependant vous pouvez aussi très simplement créer vos dépôts vous-même.

Pour cela, il suffit de créer un dossier (ce sera votre dépôt), la convention habituelle veut que vous le nommiez en .git (comme ‘projet.git’). N’oubliez pas la gestion des permissions de lecture/écriture, vous pouvez utiliser la commande push avec une adresse se connectant en root mais ceci n’est vraiment pas recommandé, normalement on utilise git avec l’utilisateur git créé spécialement lors de l’installation (sous linux si vous avez fait un apt-get).

La seule commande nécessaire à la préparation d’un dépôt est init (avec l’attribut –bare).

mkdr projet.git
cd projet.git
git init --bare

Note : l’attribut –bare sert en fait à ne pas préparer le work tree permettant de ‘scanner’ l’arborescence de fichier (en vue de créer l’index pour le stage). Le serveur n’a en effet pas besoin de stage (pour faire des commit).

Note : si vous voulez utiliser un système avec plusieurs utilisateurs vous pouvez utiliser l’attribut –shared (voir la documentation).

Et c’est tout, comme vous pouvez le voir ce fut très simple. Lors d’un futur appel d’un client sur le serveur, git regardera si un dépôt est bien présent et pour ce dossier en effet nous avons préparé un dépôt (totalement vide).

Note : vous vous demandez peut être si votre firewall risque de bloquer git sur votre serveur. Selon la méthode d’accès (le protocole dans l’adresse de votre dépôt) vous devrez ouvrir le port nécessaire. Souvent (en fait par défaut) le protocole SSH est utilisé (port 22), exemple d’adresse : git@mycompany.com:/git/project.git. Ici le ‘git’ avant @ spécifie l’utilisateur avec lequel votre client git se connecte en SSH (le mot de passe sera demandé ensuite), vous pourriez par exemple utiliser ‘root@[…]’ (non recommandé). Plusieurs protocoles d’accès sont possibles, nous ne présenterons pas ici toute les possibilités.

Note : vous pouvez aussi avoir un dépôt ‘distant’ (remote) sur le même ordinateur: auquel cas vous pouvez utiliser le protocole ‘file://’.

4.3 – Création et premier push côté client

Après l’installation de git sur votre ordinateur, il vous faut savoir qu’un commit est obligatoirement accompagné d’un message et d’une identité (nom + email). Le message sera écrit lors de l’opération de commit mais votre identité doit être préalablement enregistrée. Afin de définir votre nom et email, vous pouvez utiliser les commandes suivantes :

git config --global user.name "John Doe"
git config --global user.email johndoe@example.com

Vous n’avez pas vraiment besoin de retenir ceci car git vous rappellera ces commandes si vous ne l’avez pas encore fait.

Note : il existe aussi un système de configuration par dépôt (comme l’attribut –global le sous-entend).

Nous supposons que votre projet est dans un dossier nommé simplement ‘projet’. Nous allons créer un dépôt local à partir de ce dossier (donc synchronisant tous les fichiers, vous pouvez préparer un fichier .gitignore préalablement). Puis commit notre première version et push sur notre serveur.

cd projet
git init

Cette fois nous avons utiliser la commande init simplement afin de préparer notre dépôt local (qui scannera le dossier courant par défaut).

git add .

Nous ajoutons ensuite à l’index tous le dossier courant (le dossier . est le dossier courant).

git commit -m "Premier commit"

Puis nous pouvons commit, vous noterez que nous accompagnons le commit de son message directement avec l’attribut -m. Sinon vous serez emmené sur un éditeur par défaut afin d’éditer un fichier (temporaire) contenant le message (qui est obligatoire avec git) associé à votre commit.

git remote add origin <adresse du dépôt distant>
git push

Enfin, nous ajoutons notre dépôt distant et nous faisons un simple push.

Ici le push simple ne marchera pas car si vous vous souvenez nous n’avons pas défini notre upstream (dépôt distant pas défaut). Il est plus simple, dans un premier temps, de ne retenir que le fait de devoir faire un push, git vous rappellera élégamment la commande adéquate afin d’effectuer le push sur votre dépôt ‘origin’ (nom que nous avons donné) tout en l’inscrivant comme upstream (ainsi les futurs push simple fonctionneront). Cette dernière commande étant en fait :

git push --set-upstream origin master

4.4 Récupération du projet

Pour récupérer un projet, nous devrions suivre un processus tel que : init -> add remote -> checkout. Cependant, git nous propose un raccourci avec la commande clone.

git clone <adresse du dépôt distant>

5 – Conclusion et perspectives

Comme nous l’avons vu, git est en effet un système complexe : il est composé de plusieurs couches qu’il est nécessaire de comprendre pour ne pas s’y perdre. Mais la complexité n’est pas synonyme de difficulté : chaque couche est en fait proprement structurée et les commandes sont claires et simples (c’est bien leur multiplicité qui rend le système globalement difficile pour débuter).

Il y a encore beaucoup de choses à apprendre sur les possibilités de git mais vous devriez avoir une bonne compréhension du système après cet article.

Bonus – Migration de SVN vers git

Si vous aviez déjà un dépôt SVN, git propose une commande simple afin de migrer celui-ci en git. Ici vous est montré la version la plus simple, vous pouvez aussi récupérer le nom d’utilisateur SVN (voir la documentation officielle) :

git svn clone <adresse du dépôt SVN> --no-metadata
cd <dossier du projet>
git remote add origin <adresse du dépôt distant git>
git push
git push --set-upstream origin master