Ce chapitre propose une mise en application de quelques principes centraux du langage Git vus précédemment.

Author

Lino Galiana

Published

September 30, 2020

Download nbviewer Onyxia Onyxia
Binder Open In Colab githubdev

Les exercices suivants sont inspirés d’un cours de Git que j’ai participé à construire à l’Insee et dont les ressources sont disponibles ici. L’idée du cadavre exquis est inspirée de cette ressource et de celle-ci.

Cette partie part du principe que les concepts généraux de Git sont maîtrisés et qu’un environnement de travail fonctionnel avec Git est disponible. Un exemple de tel environnement est le JupyterLab ou l’environnement VSCode du SSPCloud où une extension Git est pré-installée :

Onyxia

Outre le chapitre précédent, il existe de nombreuses ressources sur internet sur le sujet, notamment une série de ressources construites pour l’Insee sur ce site et des ressources de la documentation collaborative sur R qu’est utilitR (des éléments sur la configuration et pratique sur RStudio). Toutes les ressources ne sont donc pas du Python car Git est un outil transversal qui doit servir quel que soit le langage de prédilection.

Git fait parti des pratiques collaboratives devenues standards dans le domaine de l’open-source mais également de plus en plus communes dans les administrations et entreprises de la data science.

Ce chapitre propose, pour simplifier l’apprentissage, d’utiliser l’ extension Git de JupyterLab ou de VSCode. Un tutoriel présentant l’extension JupyterLab est disponible ici. VSCode propose probablement, à l’heure actuelle, l’ensemble le plus complet.

Certains passages de ce TD nécessitent d’utiliser la ligne de commande. Il est tout à fait possible de réaliser ce TD entièrement avec celle-ci. Cependant, pour une personne débutante en Git, l’utilisation d’une interface graphique peut constituer un élément important pour la compréhension et l’adoption de Git. Une fois à l’aise avec Git, on peut tout à fait se passer des interfaces graphiques pour les routines quotidiennes et ne les utiliser que pour certaines opérations où elles s’avèrent fort pratiques (notamment la comparaison de deux fichiers avant de devoir fusionner).

Configuration du compte Github

Rappels sur la notion de dépôt distant

Comme expliqué dans le chapitre précédent, il convient de distinguer le dépôt distant (remote) et la copie ou les copies locales (les clones) d’un dépôt. Le dépôt distant est généralement stocké sur une forge logicielle (Github ou Gitlab) et sert à centraliser la version collective d’un projet. Les copies locales sont des copies de travail.

Git est un système de contrôle de version asynchrone, c’est-à-dire qu’on n’interagit pas en continu avec le dépôt distant (comme c’est le cas dans le système SVN) mais qu’il est possible d’avoir une version locale qui se différencie du dépôt commun et qu’on rend cohérente de temps en temps.

Bien qu’il soit possible d’avoir une utilisation hors-ligne de Git, c’est-à-dire un pur contrôle de version local sans dépôt distant, cela est une utilisation rare et qui comporte un intérêt limité. L’intérêt de Git est d’offrir une manière robuste et efficace d’interagir avec un dépôt distant facilitant ainsi la collaboration en équipe ou en solitaire.

Pour ces exercices, il est proposé d’utiliser Github, la forge la plus visible. L’avantage de Github par rapport à son principal concurrent, Gitlab, est que le premier est plus visible, car mieux indexé par Google et concentre, en partie pour des raisons historiques, plus de développeurs Python et R (ce qui est important dans des domaines comme le code où les externalités de réseau jouent).

Première étape: créer un compte Github

Les deux premières étapes se font sur Github.

Deuxième étape: créer un token (jeton) HTTPS

Principe

Git est un système décentralisé de contrôle de version : les codes sont modifiés par chaque personne sur son poste de travail, puis sont mis en conformité avec la version collective disponible sur le dépôt distant au moment où le contributeur le décide.

Il est donc nécessaire que la forge connaisse l’identité de chacun des contributeurs, afin de déterminer qui est l’auteur d’une modification apportée aux codes stockés dans le dépôt distant. Pour que Github reconnaisse un utilisateur proposant des modifications, il est nécessaire de s’authentifier (un dépôt distant, même public, ne peut pas être modifié par n’importe qui). L’authentification consiste ainsi à fournir un élément que seul vous et la forge êtes censés connaître : un mot de passe, une clé compliquée, un jeton d’accès…

Plus précisément, il existe deux modalités pour faire connaître son identité à Github :

  • une authentification HTTPS (décrite ici) : l’authentification se fait avec un login et un mot de passe ou avec un token (un mot de passe compliqué généré automatiquement par Github et connu exclusivement du détenteur du compte Github) ;
  • une authentification SSH : l’authentification se fait par une clé cryptée disponible sur le poste de travail et que GitHub ou GitLab connaît. Une fois configurée, cette méthode ne nécessite plus de faire connaître son identité : l’empreinte digitale que constitue la clé suffit à reconnaître un utilisateur.

La documentation collaborative utilitR présente les raisons pour lesquelles il convient de favoriser la méthode HTTPS sur la méthode SSH.

Créer un jeton

La documentation officielle comporte un certain nombre de captures d’écran expliquant comment procéder.

Nous allons utiliser le credential helper associé à Git pour stocker ce jeton. Ce credential helper permet de conserver de manière pérenne un jeton (on peut aussi faire en sorte que le mot de passe soit automatiquement supprimé de la mémoire de l’ordinateur au bout, par exemple, d’une heure).

L’inconvénient de cette méthode est que Git écrit en clair le jeton dans un fichier de configuration. C’est pour cette raison qu’on utilise des jetons puisque, si ces derniers sont révélés, on peut toujours les révoquer et éviter les problèmes (pour ne pas stocker en clair un jeton il faudrait utiliser une librairie supplémentaire comme libsecrets qui est au-delà du programme de ce cours).

Si vous désirez conserver de manière plus durable ou plus sécurisée votre jeton (en ne conservant pas le jeton en clair mais de manière hashée), est d’utiliser un gestionnaire de mot de passe comme Keepass (recommandé par l’Anssi). Néanmoins, il est recommandé de tout de même fixer une date d’expéritation aux jetons pour limiter les risques de sécurité d’un token qui fuite sans s’en rendre compte.

Premier commit

A ce stade, nous avons configuré Git pour être en mesure de s’authentifier automatiquement et nous avons cloné le dépôt pour avoir une copie locale de travail.

On n’a encore ajouté aucun fichier à Git en supplément de ceux créés en même temps que le dépôt. Nous allons créer ces premiers fichiers

Il est temps de valider notre modification. Cette opération s’appelle commit en langage Git et, comme son nom l’indique, il s’agit d’une proposition de modification sur laquelle, en quelques sortes, on s’engage.

Un commit comporte un titre et éventuellement une description. A ces informations, Git ajoutera automatiquement quelques éléments supplémentaires, notamment l’auteur du commit (pour identifier la personne ayant proposé cette modification) et l’horodatage (pour identifier le moment où cette modification a été proposée). Ces informations permettront d’identifier de manière unique le commit auquel sera ajouté un identifiant aléatoire unique (un numéro SHA) qui permettra de faire référence à celui-ci sans ambiguïté.

Le titre est important car il s’agit, pour un humain, du point d’entrée dans l’histoire d’un dépôt (voir par exemple l’histoire du dépôt du cours. Les titres vagues (Mise à jour du fichier, Update…) sont à bannir car ils nécessiteront un effort inutile pour comprendre les fichiers modifiés.

N’oubliez pas que votre premier collaborateur est votre moi futur qui, dans quelques semaines, ne se souviendra pas en quoi consistait le commit Update du 12 janvier et en quoi il se distingue du Update du 13 mars.

❓ Question : à ce stade, le dépôt du projet sur GitHub (remote) a-t-il été modifié ?

Le fichier .gitignore

Lorsqu’on utilise Git, il y a des fichiers qu’on ne veut pas partager ou dont on ne veut pas suivre les modifications (typiquement les grosses bases de données).

C’est le fichier .gitignore qui gère les fichiers exclus du contrôle de version. Lors de la création du projet sur GitHub, nous avons demandé la création d’un fichier .gitignore, qui se situe à la racine du projet. Il spécifie l’ensemble des fichiers qui seront toujours exclus de l’indexation faite par Git.

Question : que se passe-t-il lorsque l’on ajoute au .gitignore des fichiers qui ont déjà été commit sur le projet Git ?

Premières interactions avec Github depuis sa copie de travail

Jusqu’à présent, après avoir cloné le dépôt, on a travaillé uniquement sur notre copie locale. On n’a pas cherché à interagir à nouveau avec Github.

Cependant, il existe bien une connexion entre notre dossier local et le dépôt Github. Si on utilise la ligne de commande, on peut s’en assurer en tapant dans un terminal

git remote -v

Le dépôt distant s’appelle remote en langage Git. L’option -v (verbose) permet de lister le(s) dépôt(s) distant(s). Le résultat devrait avoir la structure suivante :

origin  https://github.com/<username>/<projectname>.git (fetch)
origin  https://github.com/<username>/<projectname>.git (push)

Plusieurs informations sont intéressantes dans ce résultat. D’abord on retrouve bien l’url qu’on avait renseigné à Git lors de l’opération de clonage. Ensuite, on remarque un terme origin. C’est un alias pour l’url qui suit. Cela évite d’avoir, à chaque fois, à taper l’ensemble de l’url, ce qui peut être pénible et source d’erreur.

fetch et push sont là pour nous indiquer qu’on récupère (fetch) des modifications d’origin mais qu’on envoie également (push) des modifications vers celui-ci. Généralement, les url de ces deux dépôts sont les mêmes mais cela peut arriver, lorsqu’on contribue à des projets opensource qu’on n’a pas créé, qu’ils diffèrent1.

Envoyer des modifications sur le dépôt distant: push

La fonctionnalité pull

La deuxième manière d’interagir avec le dépôt est de récupérer des résultats disponibles en ligne sur sa copie de travail. On appelle cela pull.

Pour le moment, vous êtes tout seul sur le dépôt. Il n’y a donc pas de partenaire pour modifier un fichier dans le dépôt distant. On va simuler ce cas en utilisant l’interface graphique de Github pour modifier des fichiers. On rapatriera les résultats en local dans un deuxième temps.

L’opération pull permet :

  1. A votre système local de vérifier les modifications sur le dépôt distant que vous n’auriez pas faites (cette opération s’appelle fetch)
  2. De les fusionner s’il n’y a pas de conflit de version ou si les conflits de version sont automatiquement fusionnables (deux modifications d’un fichier mais qui ne portent pas sur le même emplacement).

Même tout seul, ne pas se limiter à main

Au début d’une tâche particulière ou d’un projet, il est recommandé d’ouvrir des issues. Prenant la forme d’un espace de discussion, elles correpondront à la fin à des nouvelles fonctionnalités (en anglais, features). Les issues permettent également de signaler des bugs constatés, de se les répartir et d’indiquer s’ils sont réglés ou s’ils ont avancés. Une utilisation intensive des issues, avec des labels adéquats, peut même amener à se passer d’outils de gestion de projets comme Trello.

La branche main est la branche principale. Elle se doit d’être “propre”. Si on veut être rigoureux, on ne pousse pas des travaux non aboutis sur main.

Il est possible de pousser directement sur main dans le cas de petites corrections, de modifications mineures dont vous êtes certains qu’elles vont fonctionner. Mais sachez que dans le cadre de projets sensibles, c’est strictement interdit. N’ayez pas peur de fixer comme règle l’interdiction de pousser sur main, cela obligera l’équipe projet à travailler professionnellement.

Au moindre doute, créez une branche. Les branches sont utilisées pour des travaux significatifs :

  • vous travaillez seul sur une tâche qui va vous prendre plusieurs heures ou jours de travail (vous ne devez pas pousser sur main des travaux non aboutis);
  • vous travaillez sur une fonctionnalité nouvelle et vous souhaiterez recueillir l’avis de vos collaborateurs avant de modifier main;
  • vous n’êtes pas certain de réussir vos modifications du premier coup et préférez faire des tests en parallèle.

L’option de fusion Squash and Merge permet de regrouper tous les commits d’une branche (potentiellement très nombreux) en un seul dans la branche de destination. Cela évite, sur les gros projets, des branches avec des milliers de commits.

Je recommande de toujours utiliser cette technique et non les autres. Pour désactiver les autres techniques, vous pouvez aller dans Settings et dans la partie Merge button ne conserver cochée que la méthode Allow squash merging

Un cadavre exquis pour découvrir le travail collaboratif

Jusqu’à présent, nous avons découvert les vertus de Git dans un projet individuel. Nous allons maintenant aller plus loin dans un projet collectif.

Le workflow adopté

Nous allons adopter le mode de travail le plus simple, le Github Flow. Il correspond à cette forme caractéristique d’arbre:

  1. La branche main constitue le tronc
  2. Les branches partent de main et divergent
  3. Lorsque les modifications aboutissent, elles sont intégrées à main ; la branche en question disparaît :

Il existe des workflows plus complexes, notamment le Git Flow que j’utilise pour développer ce cours. Ce tutoriel, très bien fait, illustre avec un graphique la complexité accrue de ce flow :

Cette fois, une branche intermédiaire, par exemple une branche development intègre des modifications à tester avant de les intégrer dans la version officielle (main).

Méthode pour les merges

Les merges vers main doivent impérativement passer par Github (ou Gitlab). Cela permet de garder une trace explicite de ceux-ci (par exemple ici), sans avoir à chercher dans l’arborescence, parfois complexe, d’un projet.

La bonne pratique veut qu’on fasse un squash commit pour éviter une inflation du nombre de commits dans main: les branches ont vocation à proposer une multitude de petits commits, les modifications dans main doivent être simples à tracer d’où le fait de modifier des petits bouts de code.

Comme on l’a fait dans un exercice précédent, il est très pratique d’ajouter dans le corps du message close #xxxx est le numéro d’une issue associée à la pull request. Lorsque la pull request sera fusionnée, l’issue sera automatiquement fermée et un lien sera créé entre l’issue et la pull request. Cela vous permettra de comprendre, plusieurs mois ou années plus tard comment et pourquoi telle ou telle fonctionnalité a été implémentée.

En revanche, l’intégration des dernières modifications de main vers une branche se fait en local. Si votre branche est en conflit, le conflit doit être résolu dans la branche et pas dans main. main doit toujours rester propre.

Mise en pratique

Git permet donc de travailler, en même temps, sur le même fichier et de limiter le nombre de gestes manuels nécessaires pour faire la fusion. Lorsqu’on travaille sur des bouts différents du même fichier, on n’a même pas besoin de faire de modification manuelle, la fusion peut être automatique.

Git est un outil très puissant. Mais, il ne remplace pas une bonne organisation du travail. Vous l’avez vu, ce mode de travail uniquement sur main peut être pénible. Les branches prennent tout leur sens dans ce cas.

Back to top

Footnotes

  1. Ce cas de figure arrive lorsqu’on contribue à des projets sur lesquels on n’a pas de droit d’écriture. Il est alors nécessaire d’effectuer un fork, une copie de ce dépôt sur laquelle on dispose de droits. Dans ce cas de figure, on rencontre généralement un nouvel alias à côté d’origin. nommé upstream (cf. le tutoriel Github pour mettre à jour un fork et qui pointe vers le dépôt source à l’origine du fork. La création du bouton Fetch upstream par Github facilite grandement la mise en cohérence d’upstream et origin et constitue la méthode recommandée.↩︎

  2. La commande checkout est un couteau-suisse de la gestion de branche en Git. Elle permet en effet de basculer d’une branche à l’autre, mais aussi d’en créer, etc.↩︎

Citation

BibTeX citation:
@book{galiana2023,
  author = {Galiana, Lino},
  title = {Python Pour La Data Science},
  date = {2023},
  url = {https://pythonds.linogaliana.fr/},
  doi = {10.5281/zenodo.8229676},
  langid = {en}
}
For attribution, please cite this work as:
Galiana, Lino. 2023. Python Pour La Data Science. https://doi.org/10.5281/zenodo.8229676.