diff --git a/build/build_light.R b/build/build_light.R index eca133f10..6e6fbfc27 100644 --- a/build/build_light.R +++ b/build/build_light.R @@ -6,7 +6,7 @@ library(reticulate) content_rmd <- readLines("diff.txt") content_rmd <- content_rmd[startsWith(content_rmd, "content/course")] content_rmd <- content_rmd[endsWith(content_rmd, ".Rmd")] -content_rmd <- content_rmd[!grepl("/git/", content_rmd)] +#content_rmd <- content_rmd[!grepl("/git/", content_rmd)] content_rmd <- content_rmd[!grepl("06a_exo_supp_webscraping.", content_rmd)] file.remove( diff --git a/content/course/git/_index.md b/content/course/git/_index.md index 58bf657a9..32a7a9b4c 100644 --- a/content/course/git/_index.md +++ b/content/course/git/_index.md @@ -24,27 +24,28 @@ Une grande partie du contenu de ce chapitre provient du cours ## Pourquoi faire du `Git` ? -Tous les statisticiens se sont déjà demandé (ou à leurs collègues) : +Tous les statisticiens se sont déjà demandé (ou ont demandé à leurs collègues) : * quelle était la bonne version d'un programme * qui était l'auteur d'un bout de code en particulier * si un changement était important ou juste un essai -* comment fusionner des programmes +* où retrouver des traces d'un vieil essai abandonné mais potentiellement finalement prometteur +* comment fusionner des programmes écrits par plusieurs personnes * etc. -Il existe un outil informatique puissant afin de répondre à tous ces besoins : la gestion de version (*version control system* (VCS) en anglais). Ses avantages sont incontestables et permettent de facilement : +Il existe un outil informatique puissant qui répond à tous ces besoins : la gestion de version (*version control system* (VCS) en anglais). Ses avantages sont incontestables et permettent de facilement : * enregistrer l'historique des modifications d'un ensemble de fichiers * revenir à des versions précédentes d'un ou plusieurs fichiers * rechercher les modifications qui ont pu créer des erreurs * partager ses modifications et récupérer celles des autres -* proposer des modifications, les discuter, sans pour autant modifier la dernière version existante +* proposer des modifications, les discuter, sans pour autant modifier d'emblée la dernière version existante * identifier les auteurs et la date des modifications En outre, ces outils fonctionnent avec tous les langages informatiques (texte, R, Python, SAS, $\LaTeX$, Java, etc.) car reposent sur la comparaison des lignes et des caractères des programmes. -On peut ainsi résumé les principaux avantages du contrôle de version +On peut ainsi résumer les principaux avantages du contrôle de version de la manière suivante : 1. Conserver et archiver l'ensemble des versions d'un code ou d'une documentation @@ -55,9 +56,9 @@ de la manière suivante : ### Conserver et archiver du code -Une des principales fonctionnalités de la gestion de version est conserver l'ensemble des fichiers de façon sécurisée et de proposer un archivage structuré des codes. Les fichiers sont stockés dans un **dépôt**, qui constitue le projet +Une des principales fonctionnalités de la gestion de version est de conserver l'ensemble des fichiers de façon sécurisée et de proposer un archivage structuré des codes. Les fichiers sont stockés dans un **dépôt**, qui constitue le projet. -Tout repose dans la gestion et la présentation de l'historique des modifications. Chaque modification (ajout, suppression ou changement) sur un ou plusieurs fichiers est identifiée par son auteur, sa date et un bref descriptif^[Plus précisément, chaque modification est identifiée de manière unique par un code `SHA` auquel est associé l'auteur, l'horodatage et des méta-données (par exemple le message descriptif associé)]. Chaque changement est donc unique et aisément identifiable quand ils sont classés par ordre chronologique. Les modifications transmises au dépôt sont appelées **commit**. +Tout repose dans la gestion et la présentation de l'historique des modifications. Chaque modification (ajout, suppression ou changement) sur un ou plusieurs fichiers est identifiée par son auteur, sa date et un bref descriptif^[Plus précisément, chaque modification est identifiée de manière unique par un code `SHA` auquel est associé l'auteur, l'horodatage et des métadonnées (par exemple le message descriptif associé)]. Chaque changement est donc unique et aisément identifiable quand les modifications sont classées par ordre chronologique. Les groupes de modifications transmis au dépôt sont appelées **commit**. Avec des outils graphiques, on peut vérifier l' [ensemble des évolutions d'un fichier (`history`)](https://github.com/linogaliana/python-datascientist/commits/master/README.md), @@ -66,7 +67,7 @@ On peut aussi [se concentrer sur une modification particulière d'un fichier](https://github.com/linogaliana/python-datascientist/commit/7e5d30ae0e260f9485453b42f195b0181a53e32e#diff-04c6e90faac2675aa89e2176d2eec7d8) ou vérifier, pour un fichier, la [modification qui a entraîné l'apparition de telle ou telle ligne (`blame`)](https://github.com/linogaliana/python-datascientist/blame/master/README.md) -Sur son poste de travail, les dizaines (centaines ?) de programmes organisés à la main n'existent plus. Tout est regroupé dans un seul dossier, rassemblant les éléments du dépôt. Au sein du dépôt, tout l'historique est stocké et accessible rapidement. Si on souhaite travailler sur la dernière version des programmes (ou sur une ancienne version spécifique), il n'y a plus besoin de conserver les autres fichiers car ils sont dans l'historique du projet. Il est alors possible de choisir sur quelle version on veut travailler (la dernière commune à tout le monde, la sienne en train d'être développée, celle de l'année dernière, etc.). +Sur son poste de travail, les dizaines (voire centaines) de programmes organisés à la main n'existent plus. Tout est regroupé dans un seul dossier, rassemblant les éléments du dépôt. Au sein du dépôt, tout l'historique est stocké et accessible rapidement. Si on souhaite travailler sur la dernière version des programmes (ou sur une ancienne version spécifique), il n'y a plus besoin de conserver les autres fichiers car ils sont dans l'historique du projet. Il est alors possible de choisir sur quelle version on veut travailler (la dernière commune à tout le monde, la sienne en train d'être développée, celle de l'année dernière, etc.). ### Travailler efficacement en équipe @@ -84,10 +85,10 @@ correction A ces avantages s'ajoutent les fonctionalités collaboratives des sites de dépôt (les principaux étant `Github` et `Gitlab`), qui permettent d'intéragir via -des *issues*, faire des suggestions de modifications, etc. +des [*issues*](https://github.com/linogaliana/python-datascientist/issues), faire des suggestions de modifications, etc. -L'usage individuel, c'est-à-dire seul sur son projet, permet aussi de "travailler en équipe avec soi même" car il permet de retrouver des mois plus tard le contenu et le contexte des modifications. Cela est notamment précieux lors des changements de poste ou des travaux réguliers mais espacés dans le temps (par exemple, un mois par an chaque année). Même lorsqu'on travaille tout seul, on collabore avec un *moi* futur qui peut ne plus se souvenir de la modification des fichiers. +L'usage individuel, c'est-à-dire seul sur son projet, permet aussi de "travailler en équipe avec soi-même" car il permet de retrouver des mois plus tard le contenu et le contexte des modifications. Cela est notamment précieux lors des changements de poste ou des travaux réguliers mais espacés dans le temps (par exemple, un mois par an chaque année). Même lorsqu'on travaille tout seul, on collabore avec un *moi* futur qui peut ne plus se souvenir de la modification des fichiers. ### Améliorer la qualité des codes @@ -105,7 +106,7 @@ ceux-ci à tourner sur des machines autres que celles du développeur du code. Les sites de dépôts `Github` et `Gitlab` permettent de faire beaucoup plus que seulement archiver des codes. Les fonctionalités de déploiement -en continu permettent ainsi de: +en continu permettent ainsi de : * créer des sites web pour valoriser des projets (par exemple les sites `pkgdown` en `R`) diff --git a/content/course/git/exogit.Rmd b/content/course/git/exogit.Rmd index 3a5ea376c..7fd77e158 100644 --- a/content/course/git/exogit.Rmd +++ b/content/course/git/exogit.Rmd @@ -1,4 +1,15 @@ --- +jupyter: + jupytext: + text_representation: + extension: .Rmd + format_name: rmarkdown + format_version: '1.2' + jupytext_version: 1.6.0 + kernelspec: + display_name: Python 3 + language: python + name: python3 title: "Un cadavre exquis pour découvrir Git" date: 2020-09-30T13:00:00Z draft: false @@ -10,7 +21,8 @@ categories: - Exercice type: book summary: | - Mise en pratique des concepts du langage Git + Ce chapitre propose une mise en application de quelques principes + centraux du langage Git vus précédemment --- -## Git tout seul - -### Première étape: avoir un compte `Github` - -Les deux premières étapes se font sur `Github` - -```{=html} -{{% panel "exercise" "Exercise 1: créer un compte Github" "fab fa-github" %}} +```{r setup, include=FALSE} +dir_path <- gsub(here::here(), "..", here::here("course","git")) +knitr::knit_hooks$set( + plot = function(x, options) modif_plot(x, options, dir_path = dir_path) +) +knitr::opts_chunk$set(eval = FALSE) +``` -1. Si vous n'en avez pas déjà un, créer un compte sur `github.com` -2. Créer un dépôt vide. Ce dépôt sera personnel, vous pouvez le rendre public -ou non, comme vous le souhaitez. -{{% /panel %}} +```{r, echo = FALSE, results = 'asis', include = TRUE, eval = TRUE} +print_badges() ``` +Les exercices suivants sont inspirés d'un cours de Git que j'ai construit +à l'Insee et dont les ressources sont disponibles +[ici](https://linogaliana.gitlab.io/collaboratif/git.html). L'idée +du cadavre exquis, qui m'a été inspirée par +[Romain Lesur](https://github.com/RLesur) est inspirée de +[cette ressource](https://github.com/corent01/03-Swartz/blob/master/Parcours/01-La-prairie/git/exercice-git-cadavre-exquis.md) et de [celle-ci](https://github.com/simplonco/cadavre-request) + +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 du +SSPCloud où une extension +`Git` est pré-installée: + +[![Onyxia](https://img.shields.io/badge/SSPcloud-Tester%20via%20SSP--cloud-informational&color=yellow?logo=Python)](https://datalab.sspcloud.fr/launcher/inseefrlab-helm-charts-datascience/jupyter?onyxia.friendlyName=«python-datascientist»&resources.requests.memory=«4Gi»&security.allowlist.enabled=false&init.personalInit=«https://raw.githubusercontent.com/linogaliana/python-datascientist/master/init_onyxia.sh») + +Outre le [chapitre précédent](#introgit), il existe de +nombreuses ressources sur internet sur le sujet, notamment +[le cours de Git déjà cité](https://linogaliana.gitlab.io/collaboratif/git.html) +et des ressources `utilitR` +([des éléments sur la configuration](https://www.book.utilitr.org/git-config.html) +et [pratique sur RStudio](https://www.book.utilitr.org/git.html)). + +L'idée de ce chapitre est d'amener, progressivement, à la mise en oeuvre +de 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 d'utiliser l'extension Git de JupyterLab. +Un tutoriel présentant cette extension est disponible +[ici](https://annefou.github.io/jupyter_publish/02-git/index.html). +Les principaux IDE disponibles (Visual Studio, +PyCharm, RStudio) présentent des fonctionalités similaires. Il est +tout à fait possible d'en utiliser un autre. VisualStudio 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 + +Pour rappel, comme expliqué précédemment, 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 +qu'on utilise pour faire évoluer un projet: + +![](https://www.book.utilitr.org/pics/git/gitlab.png) + +`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 limite. 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, je propose d'utiliser `Github` dont les fonctionalités -nous suffiront amplement. Si, -dans le futur, les fonctionalités ne vous conviennent pas (sans l'apport de fonctionalités -externes, `Github` propose moins de fonctionalités que `Gitlab`) ou vous êtes -mal à l'aise avec le possesseur de `Github` (Microsoft), vous pourrez utiliser -`Gitlab` , le concurrent. +nous suffiront amplement[^1]. Si, +dans le futur, les fonctionnalités ne vous conviennent pas (sans l'apport de fonctionnalités +externes, `Github` propose moins de fonctionalités que `Gitlab`) ou si vous êtes +mal à l'aise concernant le possesseur de `Github` (Microsoft), vous pourrez utiliser +`Gitlab` , son concurrent. L'avantage de `Github` par rapport à `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). Le débat `Github` vs `Gitlab` n'a -plus beaucoup de sens aujourd'hui car les fonctionalités ont convergé (`Github` +plus beaucoup de sens aujourd'hui car les fonctionnalités ont convergé (`Github` a rattrapé une partie de son retard sur l'intégration continue) et, de toute -manière, on peut tout à fait connecter des dépôts Gitlab et Github. +manière, on peut tout à fait connecter des dépôts `Gitlab` et `Github`. -### Pratique en local -Maintenant, en local. Il faut ouvrir une invite de commande `git bash` (ou une -interface graphique connectée à `git bash`) +[^1]: Dans sa version en ligne, `Github` () +dispose de plus de visibilité que `Gitlab` (). +L'avantage que comportait Gitlab par rapport à Github +à une époque, à savoir la possibilité de disposer gratuitement de ressources +pour faire de l'intégration continue, c'est résorbé depuis que Github +a lancé son service Github Actions. Cependant, être familiarisé à +l'environnement Gitlab reste utile car beaucoup de forges logicielles +internes reposent sur les fonctionalités open-source (l'interface graphique +en faisant parti) de `Gitlab`. Il est donc fort utile de maîtriser +les fonctionalités coeur de ces deux interfaces qui sont en fait quasi-identiques. -```{=html} -{{% panel status="exercise" title="Exercise 2: découvrir l'invite de commande" icon="fas fa-pencil-alt" %}} -1. Sur les postes ENSAE. Aller dans `Scientific Apps/Git`. Vous devriez voir -un raccourci `bash.exe`. Vous pouvez lancer l'application ; elle ouvre une -invite de commande -2. Créer un dossier de travail, par exemple `Desktop/gitexo`. Dans `git bash`, -faire +## Première étape: créer un compte `Github` -~~~shell -# remplacer par le dossier qui vous intéresse -cd 'Desktop/gitexo' -~~~ +Les deux premières étapes se font sur `Github` -3. Initialiser le contrôle de version en tapant dans l'invite de commande +{{% panel status="exercise" title="Exercice" icon="fab fa-github" %}} -~~~shell -git init -~~~ +**Exercice 1: créer un compte Github** + +1. Si vous n'en avez pas déjà un, créer un compte sur https://github.com +2. Créer un dépôt vide. Créez ce dépôt **privé**, cela permettra +dans l'exercice 2 d'activer notre jeton. Vous pourrez le rendre public +après l'exercice 2, c'est comme vous le souhaitez. {{% /panel %}} + +## 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 sont 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 (qu’il faut renseigner à chaque interaction avec le dépôt), ou avec un token (méthode à privilégier). +* 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`](https://www.book.utilitr.org/git-config.html#interaction-avec-un-d%C3%A9p%C3%B4t-distant-principe) présente les raisons pour lesquelles il convient de favoriser +la méthode HTTPS sur la méthode SSH. + +Depuis août 2021, `Github` n'autorise plus l'authentification par mot de passe +lorsqu'on interagit (`pull`/`push`) avec un dépôt distant +([raisons ici](https://github.blog/changelog/2021-08-12-git-password-authentication-is-shutting-down/)). +Il est nécessaire d'utiliser un *token* (jeton d'accès) qui présente l'avantage +d'être révoquable (on peut à tout moment supprimer un jeton si, par exemple, +on suspecte qu'il a été diffusé par erreur) et à droits limités +(le jeton permet certaines opérations standards mais +n'autorise pas certaines opérations déterminantes comme la suppression +d'un dépôt) + + +{{% panel status="note" title="Note" icon="fa fa-comment" %}} +Il est important de ne jamais stocker un _token_, et encore moins son mot de passe, dans un projet. +Il est possible de stocker un mot de passe ou *token* de manière sécurisée et durable +avec le *credential helper* de `Git`. Celui-ci est présenté par la suite. + +S'il n'est pas possible d'utiliser le *credential helper* de `Git`, un mot de passe +ou _token_ peut être stocké de manière sécurisé dans +un système de gestion de mot de passe comme [Keepass](https://keepass.fr/). + +Ne jamais stocker un jeton `Github`, ou pire un mot de passe, dans un fichier +texte non crypté. Les logiciels de gestion de mot de passe +(comme [Keepass](https://keepass.fr/), recommandé par l'Anssi) +sont simples +d'usage et permettent de ne conserver sur l'ordinateur qu'une version +hashée du mot de passe qui ne peut être décryptée qu'avec un mot de passe +connu de vous seuls. +{{% /panel %}} + + +## Créer un jeton + +La [documentation officielle](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) comporte un certain nombre de captures d'écran expliquant +comme 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). + +Ma recommandation, +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](https://keepass.fr/) (recommandé par l'Anssi). + +{{% panel status="exercise" title="Exercise" icon="fas fa-pencil-alt" %}} +**Créer et stocker un token** + +:one: Suivre la +[documentation officielle](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/creating-a-personal-access-token) en ne donnant que les droits `repo` au jeton (ajouter les droits +`workflow` si vous désirez que votre jeton soit utilisable pour des projets +où l'intégration continue est nécessaire) + +Pour résumer les étapes devraient être les suivantes: + +*Settings > Developers Settings > Personal Access Token > Generate a new token > "My bash script" > Expiration "01/03/2022" > cocher juste "repo" > Generate token > Le copier* + + +:two: Ouvrir un terminal depuis `Jupyter` (par exemple `File > New > Terminal`). +:three: Taper dans le terminal la commande +qui convient selon votre système d'exploitation pour activer le +`credential helper`: + +```{shell, eval = FALSE} +# Sous mac et linux +git config --global credential.helper store + +# Sous windows +git config --global credential.helper manager-core +``` + +:four: Récupérer, sur la page d'accueil de votre dépôt, l'url du dépôt distant. +Il prend la forme suivante + +`https://github.com//.git` + +Vous pouvez utiliser l'icone à droite pour copier l'url. + +:five: Retournez dans le terminal `Jupyter`. Taper + +```{shell, eval = FALSE} +git clone repo_url ``` -Pour le moment, on a uniquement initialisé le contrôle de version avec `Git`. +où `repo_url` est l'url du dépôt en question (vous pouvez utiliser +MAJ+Inser pour coller l'url précédemment copié) + +Tapez Entrée. Si vous n'avez pas d'erreur, cela signifie +que l'authentification a bien fonctionné et donc que tout va +bien. Normalement, si vous avez créé un dépôt vide dans l'exercice 1, +vous avez un message de `Git`: + +> warning: You appear to have cloned an empty repository. + +Ceci est normal, ce n'est pas une erreur. Le dossier de votre projet a bien +été créé. + +Si vous avez une erreur, suivez la consigne présentée ci-après +pour réinitialiser +votre *credential helper* + +:six: Si vous le désirez, vous pouvez changer la visibilité de votre dépôt +en le rendant public. + +{{% /panel %}} + +{{% panel status="note" title="Note" icon="fa fa-comment" %}} +Si vous avez fait une faute de frappe dans le mot de passe ou dans le jeton, il est possible de vider la mémoire +de la manière suivante, sous Mac ou Linux : + +~~~~shell +git config --global --unset credential.helper +~~~~ + +Sous Windows, si vous avez utilisé l'option `manager-core` évoquée ci-dessus, vous pouvez utiliser une interface graphique pour effacer le mot de passe ou jeton erroné. Pour cela, dans le menu démarrer, taper `Gestionnaire d'identification` (ou `Credential Manager` si Windows ne trouve pas). Dans l'interface graphique qui s'ouvre, il est possible de supprimer le mot de passe ou jeton en question. Après cela, vous devriez à nouveau avoir l'opportunité de taper un mot de passe ou jeton lors d'une authentification HTTPS. +{{% /panel %}} + + +# Git: des gains même quand on travaille tout seul + +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`. D'ailleurs, la première chose à faire est d'exclure un certain nombre de fichiers, afin de ne pas faire une erreur pénible à réparer. -```{=html} -{{% panel status="exercise" title="Exercise 3: le fichier .gitignore" icon="fas fa-pencil-alt" %}} + +{{% panel status="exercise" title="Exercice" icon="fas fa-pencil-alt" %}} + +**Exercise 3: 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. C'est le fichier `.gitignore` +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. -1. Maintenant, créer un fichier nommé `.gitignore` (:warning: ne pas changer -ce nom) via le bloc note ou votre IDE. -1. Aller sur le site . Vous pouvez +:one: Créer un fichier nommé `.gitignore` (:warning: ne pas changer +ce nom, et s'assurer que celui-ci n'a pas d'extension) via le bloc note ou votre IDE. + +:two: Aller sur le site . Vous pouvez dans la barre de recherche taper `Python`, `Pycharm`, `JupyterNotebooks`. Copier-coller dans votre `.gitignore` le contenu de la page. -1. Quand on crée de la documentation, on veut exclure les extensions `.pdf` + +:three: Quand on crée de la documentation, on veut exclure les extensions `.pdf` et `.html` qui sont des résultats à partager et non des fichiers source à suivre. Pour cela, ajouter au début du fichier `.gitignore`, les extensions: @@ -101,64 +345,278 @@ suivre. Pour cela, ajouter au début du fichier `.gitignore`, les extensions: .html ~~~ +:four: Quand on fait de l'analyse de données, on peut se retrouver avec des +fichiers sources de données (par exemple des csv). On désire généralement +les exclure pour deux raisons: + +* ce sont des fichiers très volumineux pour lesquels le contrôle de version, +ligne à ligne, est compliqué +* ils peuvent révéler une information confidentielle ou stratégique qu'on +ne désire pas révéler à un concurrent ou un inconnu + +Pour cela, ajouter au début du fichier `.gitignore`, les extensions suivantes + +~~~markdown +.csv +.xls +.xlsx +~~~ + +Cette suite d'extensions est à enrichir selon vos projets et les formats +de données que vous utilisez. + +:five: qui sont créés que l'on ne désire pas utiliser. Pour les exclure, +ajouter la ligne suivante dans le fichier `.gitignore` + +~~~markdown +*-checkpoint +~~~ {{% /panel %}} -``` + On a créé un fichier `.gitignore` mais on n'a encore rien fait jusqu'à présent. + +En effet, si en ligne de commande, on tape + +```{shell, eval = FALSE} +# Taper +# cd puis +git status +``` + +on voit apparaître le résultat suivant + +```raw +On branch master + +No commits yet + +Untracked files: + (use "git add ..." to include in what will be committed) + .gitignore +``` + + +Le fichier `.gitignore` est `Untracked` ce qui signifie qu'il n'est pas +encore contrôlé. + Il faut dire à `Git` de contrôler les évolutions de chaque fichier -(passage dans l'index). On appelle cette étape `git add`. **** +(passage dans l'index). On appelle cette étape `git add`. -```{=html} -{{% panel status="exercise" title="Exercise 4: pratique de git. Enfin..." icon="fas fa-pencil-alt" %}} -1. De temps en temps, il est bon de vérifier l'état d'un dépôt. Pour cela, faire -~~~shell -git status -~~~ +{{% panel status="exercise" title="Exercice" icon="fas fa-pencil-alt" %}} + +**Exercice 4: Indexer des modifications** -1. Dans l'invite de commande, taper + +:one: Se rendre dans l'extension `Git` de Jupyter. Vous devriez +retrouver un cadre ayant cet aspect + +```{r, echo = FALSE, eval = TRUE} +knitr::include_graphics("git-status-ensae.png") +``` + +:two: En passant votre souris au dessus du `.gitignore`, vous devriez voir +un `+` apparaître. Cliquez dessus. + +Si vous aviez privilégié la ligne de commande, ce que vous avez fait est +est équivalent à : ~~~shell git add .gitignore ~~~ -2. Retaper `git status`. Observer le changement. Les nouvelles modifications (en -l'occurrence la création du fichier et la validation de son contenu actuel) -ne sont pas encore archivées. Pour cela, il faut faire +Pour se remémorer ce que signifie `git add`, vous pouvez vous rendre +sur [mon cours dédié à Git](https://linogaliana.gitlab.io/collaboratif/git.html#le-b.a-ba). -~~~shell -git commit -m "Initial commit" -~~~ +:three: Observer le changement de statut du fichier `.gitignore`. Il est +désormais dans la partie `Staged` +En gros, vous venez de dire à Git que vous allez rendre publique une évolution +du fichier. + +Si vous étiez en ligne de commande vous auriez ce résultat après un `git status` + +```raw +On branch master + +No commits yet + +Changes to be committed: + (use "git rm --cached ..." to unstage) + new file: .gitignore +``` + + +Les nouvelles modifications (en +l'occurrence la création du fichier et la validation de son contenu actuel) +ne sont pas encore archivées. Pour cela, il va falloir faire un +`commit` (on rend publique une modification) + +:four: Avant cela, regardons les modifications qu'on va prochainement +valider. Pour cela, passez la souris au dessus du nom du fichier +`.gitignore` et cliquer sur le bouton `Diff this file`. +Une page s'ouvre et met en regard la version antérieure avec +les ajouts en vert et les suppressions en rouge. Nous retrouverons +cette visualisation avec l'interface `Github`, plus tard. + +En l'occurrence, comme le fichier n'existait pas, normalement nous n'avons que +des ajouts. + +Il est également possible d'effectuer cela avec la ligne de commande mais c'est +beaucoup moins pratique. Pour cela, la commande à appeler est `git diff` et +il est nécessaire d'utiliser l'option `cached` pour lui dire d'inspecter les +fichiers pour lesquels on n'a pas encore effectué de `commit`. En vert +apparaîtront les modifications et en rouge les suppressions mais, cette fois, +les résultats ne seront pas mis côte-à-côte ce qui est beaucoup moins +pratique. + +```{shell, eval = FALSE} +git diff --cached +``` {{% /panel %}} + +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](https://github.com/linogaliana/python-datascientist/commits/master). +Les titres vagues +(*Mise à jour du fichier*, *Update*...) sont à bannir car ils vous +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. + + + + +{{% panel status="exercise" title="Exercice" icon="fas fa-pencil-alt" %}} + +**Exercice 5: Premier commit (enfin !)** + +Tout se passe dans la partie inférieure de l'interface graphique. + +```{r, echo = FALSE, eval = TRUE} +knitr::include_graphics("git-panel-jupyter.png") ``` +:one: Entrer le titre `Initial commit` et ajouter une description +`Création du fichier .gitignore : tada :` (sans les espaces autour des `:`). +`: tada :` (sans les espaces) sera converti en emoji :tada: par `Github` quand on voudra +afficher la description du commit[^3]. + +[^3]: `:XXXXXX:` permet, dans des systèmes qui reposent sur `Markdown`, d'afficher +des emojis. Vous pouvez [trouver une liste ici](https://gist.github.com/rxaviers/7360908) + + +Le fait de nommer le premier commit *"Initial commit"* est une +habitude, vous +n'êtes pas obligé de suivre cette convention si elle ne vous plaît pas. + +:two: Cliquer sur `Commit`. Le fichier a disparu de la liste, c'est normal, +il n'a plus de modification à valider. Pour le retrouver dans la liste +des fichiers `Changed`, il faudra le modifier à nouveau + +:three: Cliquer sur l'onglet `History`. Votre `commit` apparaît à ce niveau. +Si vous cliquez dessus, vous obtenez des informations sur le `commit` + +{{% /panel %}} + +{{% panel status="note" title="Note" icon="fa fa-comment" %}} + +Si vous utilisiez la ligne de commande, la manière équivalente de faire +serait + +~~~shell +git commit -m "Initial commit" -m "Création du fichier .gitignore :tada:" +~~~ + L'option `m` permet de créer un message, qui sera disponible à l'ensemble des contributeurs du projet. Avec la ligne de commande, ce n'est pas toujours très pratique. Les interfaces graphiques permettent des messages plus développés (la bonne pratique veut qu'on écrive un message de commit comme un mail succinct: un titre et un peu d'explications, si besoin). -Le fait de nommer le premier commit *"Initial commit"* est une -habitude, vous -n'êtes pas obligé de suivre cette convention si elle ne vous plaît pas. +{{% /panel %}} + -### Premières interactions avec `Github` -```{=html} -{{% panel status="exercise" title="Exercise 5: interagir avec Github" icon="fas fa-pencil-alt" %}} +# 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`. On peut s'en assurer en tapant dans un terminal + +~~~shell +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: + +```raw +origin https://github.com//.git (fetch) +origin https://github.com//.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èrent[^2] + +[^2]: Dans ce cas, on rencontre généralement un nouvel alias à côté d'`origin`. +opensource, où on contribue avec un *workflow* plus complexe (on contribue +sur un fork qui est une copie d'un dépôt sur lequel on n'a pas de droits +de modifications mais auquel on va suggérer des modifications à partir de notre +`fork`), on retrouve souvent un deuxième alias qui est `upstream` (cf. +[le tutoriel `Github` pour mettre à jour un fork](https://docs.github.com/en/github/collaborating-with-pull-requests/working-with-forks/syncing-a-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. + +## Envoyer des modifications sur le dépôt distant: `push` + +{{% panel status="exercise" title="Exercice" icon="fas fa-pencil-alt" %}} + +**Exercise 5: interagir avec Github** -1. Maintenant, créer un fichier nommé `README.md` (:warning: ne pas changer -ce nom) via le bloc note ou votre IDE. -2. Y écrire une phrase au format, sujet-verbe-complément mais sans majuscule ni ponctuation. -Observer le statut du fichier avec `git status`. -3. Valider cette création avec le message *"j'écris comme un surréaliste* Il convient maintenant d'envoyer les fichiers sur le dépôt distant. + + + +:one: +L'objectif est d'envoyer vos modifications vers `origin`. +On va passer par la ligne de commande car les boutons `push`/`pull` +de l'extension `Jupyter` ne fonctionnent pas de manière systématique. -3. Envoyez vos modifications vers `origin` en tapant +Taper ~~~~shell git push origin master ~~~~ -`Git` va vous demander vos identifiants de connexion pour vérifier que vous -êtes bien autorisés à intéragir avec ce dépôt. Il faut les taper (:warning: -comme le créateur de `Git` était un peu paranoiaque, c'est normal -de ne pas voir le curseur avancer quand on tape des caractères pour le mot de passe, -si quelqu'un regarde votre écran il ne pourra ainsi pas savoir combien de -caractères comporte votre mot de passe) +Cela signifie: *"git envoie (`push`) mes modifications sur la +branche `master` (la branche sur laquelle on a travaillé, on reviendra +dessus) vers mon dépôt (alias +`origin`)"* + +Normalement, si vous avez utilisé le `credential helper`, `Git` ne +vous demande pas vos identifiants de connexion. Sinon, +il faut taper +votre identifiant github et **votre mot de passe correspond au personal access token nouvellement créé** ! + + + + + + +:two: Retournez voir le dépôt sur `Github`, vous devriez maintenant voir le fichier +`.gitignore` s'afficher en page d'accueil. {{% /panel %}} -``` -Retournez voir le dépôt sur `Github`, vous devriez maintenant voir le fichier -`.gitignore` et le `README` devrait s'afficher en page d'accueil. -```{=html} -{{% panel status="exercise" title="Exercise 6: rapatrier des modifs en local" icon="fas fa-pencil-alt" %}} +## 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. Nous verrons cela -lors de l'exercice suivant. Néanmoins, nous allons +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 rappatriera les résultats en local dans un deuxième temps. + +{{% panel status="exercise" title="Exercice" icon="fas fa-pencil-alt" %}} + +**Exercice 7: rapatrier des modifs en local** + +:one: Se rendre sur votre dépôt depuis l'interface . +2 manières de faire à ce niveau: -1. Modifier le `README` par l'interface de `Github` en cliquant -sur le crayon en haut à droite de l'affichage du `README`. -L'objectif est de lui -donner un titre suivant, en ajoutant, au début du document, la ligne suivante : +* Cliquer sur `Add file > Create new file` et appeler le fichier `README` +* Cliquer sur le bouton `ADD A README` qui est affiché sur la page d'accueil. +Supprimez tout autre texte si `Github` vous a suggéré un contenu pour le +`README` -~~~text + +:two: L'objectif est lui +donner au `README` un titre en ajoutant, au début du document, la ligne suivante : + +```{r printrmd, eval = TRUE, echo = FALSE, results="asis"} +cat( +"~~~markdown # Mon oeuvre d'art surréaliste +~~~") +``` + +Sautez une ligne et entrez le texte que vous désirez, sans ponctuation. Par exemple, + +~~~markdown +le chêne un jour dit au roseau ~~~ -Ajouter à ce titre le mot `:penc il2:`, ce qui -affichera :pencil2: dans `Github`. -Rédiger un titre et un message complémentaire pour faire le `commit`. Conserver +:three: Cliquez sur l'onglet `Preview` pour voir le texte mis en forme + +:four: Rédiger un titre et un message complémentaire pour faire le `commit`. Conserver l'option par défaut `Commit directly to the master branch` -3. Editer à nouveau le `README`. Ajouter une deuxième phrase et corrigez la +:five: Editer à nouveau le `README` en cliquant sur le crayon juste au dessus +de l'affichage du contenu du `README`. + +Ajouter une deuxième phrase et corrigez la ponctuation de la première. Ecrire un message de commit et valider. -4. Au dessus de l'aborescence des fichiers, vous devriez voir s'afficher le +~~~markdown +Le Chêne un jour dit au roseau : +Vous avez bien sujet d'accuser la Nature +~~~ + +:six: Au dessus de l'aborescence des fichiers, vous devriez voir s'afficher le titre du dernier commit. Vous pouvez cliquer dessus pour voir la modification que vous avez faite. -5. Les résultats sont sur le dépôt distant mais ne sont pas sur votre ordinateur -Pour les rapatrier en local, faire +:seven: Les résultats sont sur le dépôt distant mais ne sont pas sur votre +dossier de travail dans Jupyter. Il faut re-synchroniser votre copie locale +avec le dépôt distant : + +* Avec l'interface Jupyter, si cela est possible, appuyez tout simplement sur la petite flèche vers le bas, qui est celle qui a désormais la pastille orange. +* Si cette flèche n'est pas disponible ou si vous travaillez dans un autre +environnement, vous pouvez utiliser la ligne de +commande et taper ~~~shell git pull origin master ~~~ +Cela signifie: *"git récupère (`pull`) les modifications sur la +branche `master` vers mon dépôt (alias +`origin`)"* -{{% /panel %}} -``` +:eight: Regarder, sur JupyterLab, l'onglet `History`. Cliquez sur le +dernier commit et affichez les changements sur le fichier. Vous pouvez +remarquer la finesse du contrôle de version: `Git` détecte au sein de +la première ligne de votre texte que vous avez mis des majuscules +ou de la ponctuation. -```{=html} -{{% panel status="hint" title="Hint" icon="fa fa-lightbulb" %}} -`:XXXXXX:` permet, dans des systèmes qui reposent sur `Markdown`, d'afficher -des emojis. Vous pouvez [trouver une liste ici](https://gist.github.com/rxaviers/7360908) {{% /panel %}} -``` 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 +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 fusionnable (deux modifications d'un fichier mais qui ne portent pas sur le même emplacement) -### Même tout seul, ne pas se limiter à `master` +# Même tout seul, ne pas se limiter à `master` -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 +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 `master` est la branche principale. Elle se doit d'être "propre". On ne pousse pas des travaux non aboutis sur `master`, c'est très mal vu. +La branche `master` est la branche principale. Elle se doit d'être "propre". Si on veut être rigoureux, on ne pousse pas des travaux non aboutis sur `master`. -Peut-on pousser directement sur `master` ? oui, pour des petites corrections, des 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 `master` (voir section précédente), cela obligera l'équipe projet à travailler professionnellement. +Il est possible de pousser directement sur `master` 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 `master`, 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 `master` des travaux non aboutis); -- vous travaillez sur une fonctionnalité nouvelle et vous souhaiterez recueillir l'avis de vos collègues avant de modifier `master`; +- vous travaillez sur une fonctionnalité nouvelle et vous souhaiterez recueillir l'avis de vos collaborateurs avant de modifier `master`; - vous n'êtes pas certain de réussir vos modifications du premier coup et préférez faire des tests en parallèle. -```{=html} + {{% panel status="warning" title="Warning" icon="fa fa-exclamation-triangle" %}} + Les branches ne sont pas personnelles : **Toutes les branches sont publiées, le `rebase` est interdit. Le push force est également interdit.** -Il faut **absolument** bannir les usages de `push force` qui peuvent déstabiliser les copies locales des collaborateurs. S'il est nécessaire de faire un `push force`, c'est qu'il y a un problème dans la branche, à identifier et régler **sans** faire `push force` +Il faut **absolument** bannir les usages de `push force` qui peuvent déstabiliser les copies locales des collaborateurs. S'il est nécessaire de faire un `push force`, c'est qu'il y a un problème dans la branche, à identifier et régler **sans** faire `push force`. ![](https://miro.medium.com/max/400/0*XaLzNzYkA6PZjbl9.jpg) -**Tous les merges dans `master` doivent se faire par l'intermédiaire d'une merge request dans `GitLab`**. En effet, il est très mal vu de merger une branche dans master localement. +**Tous les merges dans `master` doivent se faire par l'intermédiaire d'une `pull request` dans `Github`**. En effet, il est très déconseillé de merger une branche dans master localement. {{% /panel %}} -``` -```{=html} -{{% panel status="hint" title="Hint" icon="fa fa-lightbulb" %}} -Comment nommer les branches ? Là encore, il y a énormément de conventions différentes. Une fréquemment observée est : -- pour les nouvelles fonctionnalités : `feature/nouvelle-fonctionnalite` où `nouvelle-fontionnalite` est un nom court résumant la fonctionnalité -- pour les corrections de bug : `issue-num` où `num` est le numéro de l'issue -N'hésitez pas à aller encore plus loin dans la normalisation ! -{{% /panel %}} -``` -```{=html} -{{% panel status="exercise" title="Exercise" icon="fas fa-pencil-alt" %}} -1. Ouvrir une *issue* sur `Github`. Signaler qu'il serait bien d'ajouter un emoji chat dans le README. Dans la partie de droite, cliquer sur la petite roue à côté de `Label` et cliquer sur `Edit Labels`. Créer un label `Markdown`. Retourner sur la page de l'*issue* et ajouter ce label -2. Sur votre dépôt local, créer une branche `issue-1` en faisant +{{% panel status="exercise" title="Exercice" icon="fas fa-pencil-alt" %}} + +**Exercice 8: Créer une nouvelle branche et l'intégrer dans master** + +:one: Ouvrir une *issue* sur `Github`. Signaler qu'il serait bien d'ajouter un emoji chat dans le README. Dans la partie de droite, cliquer sur la petite roue à côté de `Label` et cliquer sur `Edit Labels`. Créer un label `Markdown`. Normalement, le label a été ajouté. + +:two: Retournez sur votre dépôt local. Vous allez créer une branche nommée +`issue-1` + +Avec l'interface graphique de JupyterLab, cliquez sur `Current Branch - Master` +puis sur le bouton `New Branch`. Rentrez `issue-1` comme nom de branche +(la branche doit être créée depuis `master`, ce qui est normalement le choix +par défaut) et cliquez sur `Create Branch` + +Si vous n'utilisez pas l'interface graphique mais la ligne de commande, la +manière équivalente de faire est[^4] ~~~shell git checkout -b issue-1 ~~~~ -3. Ajouter un ou plusieurs emojis chat à la suite du titre. Valider avec `git add` et `git commit`. Faire un **deuxième commit** pour ajouter un emoji koala. Pousser les modifications locales: +[^4]: 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. + + +:three: Ouvrez `README.md` et ajoutez un emoji chat (`:cat:`) à la suite du titre. +Faites un commit en refaisant les étapes vues dans les exercices +précédents. N'oubliez pas, cela se fait en deux étapes: + +1. Ajoute les modifications à l'index en déplacant le fichier `README` dans +la partie `Staged` +2. Validation des modifications avec un `commit` + +Si vous passez par la ligne de commande, cela donnera: + +~~~shell +git add . +git commit -m "ajout emoji chat" +~~~ + + +:four: Faire un **deuxième commit** pour ajouter un emoji koala (:koala:) puis +pousser les modifications locales. + +Cela peut être fait avec l'interface +de `JupyterLab` grâce au bouton avec une flêche montante (il doit apparaître +en orange maintenant). + +Sinon, si vous utilisez la ligne de commande, vous devrez taper ~~~shell git push origin issue-1 ~~~~ -4. Dans `Github`, devrait apparaître `issue-1 had recent pushes XX minutes ago`. -Cliquer sur `Compare & Pull Request`. Donner un titre informatif à votre *pull request*. Dans le message en dessous, taper `Close #1` ce qui permettra de fermer automatiquement l'*issue 1* lorsque vous ferez le *merge*. **Ne validez pas la fusion**, on le fera dans un second temps. +:five: Dans `Github`, devrait apparaître + +> `issue-1 had recent pushes XX minutes ago`. -5. En local, retourner sur `master`: +Cliquer sur `Compare & Pull Request`. Donner un titre informatif à votre *pull request*. +Dans le message en dessous, taper + +> `- close #1` + +Le tiret est une petite astuce pour que `Github` +remplace le numéro de l'issue par le titre. + +Cliquez sur `Create Pull Request` mais +**ne validez pas la fusion**, on le fera dans un second temps. + +Le fait d'avoir mis un message `close` suivi d'un numéro d'issue `#1` +permettra de fermer automatiquement l'*issue 1* lorsque vous ferez le *merge*. +En attendant, vous avez créé un lien entre l'*issue* et la *pull request* + +Au passage, vous pouvez ajouter le label `Markdown` sur la droite. + +:six: En local, retourner sur `master`. Dans l'interface `Jupyter`, il suffit +de cliquer sur `master` dans la liste des branches. Si vous êtes +en ligne de commande, il faut faire ~~~shell git checkout master ~~~~ -Et ajouter une phrase à la suite de votre texte. Valider les modifications et les pusher. +`checkout` est une commande `Git` qui permet de naviguer d'une branche à l'autre +(voire d'un commit à l'autre). -6. Cliquer sur `Insights` en haut du dépôt puis, à gauche sur `Network`. Vous devriez voir apparaître l'arborescence de votre dépôt. On peut voir `issue-1` comme une ramification et `master` comme le tronc. +Ajouter une phrase à la suite de votre texte dans le `README.md` +(ne touchez pas au titre!). Vous pouvez remarquer que les emojis +ne sont pas dans le titre, c'est normal vous n'avez pas encore fusionné les versions -L'objectif est maintenant de ramener les modifications faites dans `issue-1` dans la branche principale. Retournez dans l'onglet `Pull Requests`. Là, changer le type de `merge` pour `Squash and Merge`, comme ci-dessous. Vous pouvez vous reporter là [**METTRE LIEN**] pour la justification. +:seven: Faire un commit et un push. En ligne de commande, cela donne -![](squashmerge.png) +~~~shell +git add . +git commit -m "ajoute un troisième vers" +git push origin master +~~~ -7. Supprimer la branche. Elle est mergée, la conserver risque d'amener à des push involontaires dessus. +:eight: Sur `Github`, cliquer sur `Insights` en haut du dépôt puis, à gauche sur `Network` (cela n'est +possible que si vous avez rendu public votre dépôt). -Faire la fusion et regarder le résultat dans la page d'accueil de `Github` (le `README`) et dans le graphique. +Vous devriez voir apparaître l'arborescence de votre dépôt. On peut voir `issue-1` comme une ramification et `master` comme le tronc. -{{% /panel %}} +L'objectif est maintenant de ramener les modifications faites dans `issue-1` dans la branche principale. Retournez dans l'onglet `Pull Requests`. Là, changer le type de `merge` pour `Squash and Merge`, comme ci-dessous (petit conseil: choisissez toujours cette méthode de *merge*). + +Une fois que cela est fait, vous pouvez retourner dans `Insights` puis `Network` pour vérifier que tout s'est bien passé comme prévu. + + +```{r, echo = FALSE, eval = TRUE} +knitr::include_graphics("squashmerge.png") ``` -```{=html} -{{% panel status="note" title="Note" icon="fa fa-comment" %}} -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. + +:nine: Supprimer la branche (*branch > delete this branch*). Puisqu'elle est mergée, elle ne servira plus. La conserver risque d'amener à des `push` involontaires dessus. + {{% /panel %}} -``` -```{=html} -{{% panel status="note" title="Note" icon="fa fa-comment" %}} + 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 `master` constitue le tronc +2. Les branches partent de `master` et divergent +3. Lorsque les modifications aboutissent, elles sont intégrées à `master` ; +la branche en question disparaît: + +![](https://linogaliana.gitlab.io/collaboratif/pics/03_git/flow4_discuss.png) + +Il existe des *workflows* plus complexes, notamment le `Git Flow` que j'utilise +pour développer ce cours. [Ce tutoriel](https://www.atlassian.com/fr/git/tutorials/comparing-workflows/gitflow-workflow), très bien fait, +illustre avec un graphique la complexité accrue de ce flow: + +![](https://wac-cdn.atlassian.com/dam/jcr:8f00f1a4-ef2d-498a-a2c6-8020bb97902f/03%20Release%20branches.svg?cdnVersion=55) + +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 (`master`). + +{{% panel status="hint" title="Hint" icon="fa fa-lightbulb" %}} +Vous pourrez trouvez des dizaines d’articles et d’ouvrages sur ce sujet dont chacun prétend avoir trouvé la meilleure organisation du travail (`Git flow`, `GitHub flow`, `GitLab flow`...). Ne lisez pas trop ces livres et articles sinon vous serez perdus (un peu comme avec les magazines destinés aux jeunes parents...). + +La méthode de travail la plus simple est le *Github flow* qu'on vous a proposé d'adopter. L'arborescence est reconnaissable: des branches divergent et reviennent systématiquement vers `master`. + +Pour des projets plus complexes dans des équipes développant des applications, on pourra utiliser d'autres méthodes de travail, notamment le `Git flow`. Il n'existe pas de règles universelles pour déterminer la méthode de travail ; l'important c'est, avant tout, de se mettre d'accord sur des règles communes de travail avec votre équipe. {{% /panel %}} -``` -## Cadavre exquis: découvrir le travail collaboratif -```{=html} -{{% panel status="exercise" title="Exercice: interactions avec le dépôt distant" icon="fas fa-pencil-alt" %}} -Cet exercice se fait par groupe de trois. Il y aura deux rôles dans ce scénario: un mainteneur et deux développeurs. +## Méthode pour les merges + +Les merges vers `master` doivent impérativement passer par `Github` (ou `Gitlab`). Cela permet de garder une trace explicite de ceux-ci (par exemple [ici](https://github.com/linogaliana/python-datascientist/pulls?q=is%3Apr+is%3Aclosed)), 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 `master`: les branches ont vocation à proposer une multitude de petits commits, les modifications dans `master` 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 #xx` où `xx` 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 `master` 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 master**. +`master` doit toujours rester propre. + +## Mise en pratique + +{{% panel status="exercise" title="Exercice" icon="fas fa-pencil-alt" %}} + +**Exercice 9: interactions avec le dépôt distant** + +Cet exercice se fait par groupe de trois ou quatre. Il y aura deux rôles dans ce scénario : + +- Une personne aura la responsabilité d'être **mainteneur** +- Deux à trois personnes seront **développeurs**. -1. Le mainteneur crée un dépôt sur `Github`. Il/Elle donne des droits au(x) développeur(s) du projet (`Settings > Manage Access > Invite a collaborator`). -2. Chaque membre du projet, crée une copie locale du projet grâce à la commande `git clone`. Pour cela, récupérer l'url HTTPS du dépôt en copiant l'url du dépôt que vous pouvez trouver, par exemple, dans la page d'accueil du dépôt, en dessous de `Quick setup — if you’ve done this kind of thing before` +:one: Le mainteneur crée un dépôt sur `Github`. Il/Elle donne des droits au(x) développeur(s) du projet (`Settings > Manage Access > Invite a collaborator`). + +:two: Chaque membre du projet, crée une copie locale du projet grâce à la commande `git clone` ou +avec le bouton `Clone a repository` de `JupyterLab`. + +Pour cela, récupérer l'url HTTPS du dépôt en copiant l'url du dépôt que vous pouvez trouver, par exemple, dans la page d'accueil du dépôt, en dessous de `Quick setup — if you’ve done this kind of thing before` + +En ligne de commande, cela donnera: ~~~shell -git clone https://XXXXXX +git clone https://github.com//.git ~~~ -3. Chaque membre du projet crée un fichier avec son nom et son prenom, selon cette structure `nom-prenom.md` en évitant les caractères spéciaux. Il écrit dedans trois phrases aléatoires, sans ponctuation ni majuscules. +:three: Chaque membre du projet crée un fichier avec son nom et son prenom, selon cette structure `nom-prenom.md` en évitant les caractères spéciaux. Il écrit dedans trois phrases de son choix **sans ponctuation ni majuscules** (pour pouvoir effectuer une correction ultérieurement. Enfin, il commit sur le projet. -4. Valider les modifications +Pour rappel, en ligne de commande cela donnera les commandes suivantes à modifier ~~~shell git add nom-prenom.md git commit -m "C'est l'histoire de XXXXX" ~~~ -5. Chacun essaie d'envoyer ses modifications locales sur le dépôt: +:four: Chacun essaie d'envoyer (*push*) ses modifications locales sur le dépôt: ~~~shell git push origin master ~~~ -A ce stade, une seule personne (la plus rapide) devrait ne pas avoir rencontré de rejet du `push`. C'est normal, avant d'accepter une modification `Git` vérifie en premier lieu la cohérence de la branche avec le dépôt distant. Le premier ayant fait un `push` a modifié le dépôt commun ; les autres doivent intégrer ces modifications dans leur version locale avant d'avoir le droit de proposer un changement. -6. Pour celui/celle/ceux dont le `push` a été refusé, faire +:five: A ce stade, une seule personne (la plus rapide) devrait ne pas avoir rencontré de rejet du `push`. C'est normal, avant d'accepter une modification `Git` vérifie en premier lieu la cohérence de la branche avec le dépôt distant. Le premier ayant fait un `push` a modifié le dépôt commun ; les autres doivent intégrer ces modifications dans leur version locale (*pull*) avant d'avoir le droit de proposer un changement. + +Pour celui/celle/ceux dont le `push` a été refusé, faire ~~~shell git pull origin master @@ -376,35 +1013,45 @@ git pull origin master pour ramener les modifications distantes en local. -7. Taper `git log` et regarder la manière dont a été intégré la modification de votre camarade ayant pu faire son `push` +:six: Taper `git log` et regarder la manière dont a été intégré la modification de votre camarade ayant pu faire son `push` + +Vous remarquerez que les commits de vos camarades sont intégrés tels quels à +l'histoire du dépôt. -8. Faire à nouveau +:seven: Faire à nouveau ~~~shell git pull origin master ~~~ -Le dernier doit refaire, à nouveau, les étapes 6 à 8 +Le dernier doit refaire, à nouveau, les étapes 5 à 7 (dans une équipe de quatre +il faudra encore le refaire une fois). + {{% /panel %}} -``` -```{=html} {{% panel status="warning" title="Warning à nouveau: ne JAMAIS FAIRE git push force" icon="fa fa-exclamation-triangle" %}} Quand on fait face à un rejet du `push`, on est tenté de faire passer en force le `push` malgré la mise en garde précédente. -Il faut immédiatement oublier cette solution, elle crée de nombreux problèmes et, en fait, ne résoud rien. L'un des risques est de réécrire entièrement l'historique rendant les copies locales, et donc les modifications de vos collaborateurs, caduques. Cela vous vaudra, à raison, des remontrances de vos partenaires qui perdent le bénéfice de leur historique `Git` qui, s'ils ont des versions sans `push` depuis longtemps peuvent avoir diverger fortement du dépôt maître. +Il faut **immédiatement oublier cette solution**, elle crée de nombreux problèmes et, en fait, ne résout rien. L'un des risques est de réécrire entièrement l'historique rendant les copies locales, et donc les modifications de vos collaborateurs, caduques. Cela vous vaudra, à raison, des remontrances de vos partenaires qui perdent le bénéfice de leur historique `Git` qui, s'ils ont des versions sans `push` depuis longtemps peuvent avoir diverger fortement du dépôt maître. {{% /panel %}} -{{% panel status="exercise" title="Exercice: gérer les conflits quand on travaille sur le même fichier" icon="fas fa-pencil-alt" %}} -Chaque personne va travailler sur les fichiers des deux autres membres. -1. Les deux développeurs ajoutent la ponctuation et les majuscules -2. Sauter une ligne et ajouter une phrase -3. Valider les résultats (`add` et `commit`) et faire un `push` -4. La personne la plus rapide n'a, normalement, rencontré aucune difficulté (elle peut s'arrêter temporairement pour regarder ce qui va se passer chez les voisins, en respectant la distanciation sociale :mask:). Les autres voient leur `push` refusé. Faire un `pull`. +{{% panel status="exercise" title="Exercice" icon="fas fa-pencil-alt" %}} + +**Exercice 10: gérer les conflits quand on travaille sur le même fichier** + +Dans la continuité de l'exercice précédent, chaque personne va travailler sur les fichiers des autres membres de l'équipe. + +:one: Les deux ou trois développeurs ajoutent la ponctuation et les majuscules du fichier du premier développeur. + +:two: Ils sautent une ligne et ajoutent une phrase (pas tous la même). + +:three: Valider les résultats (`git add .` et `commit`) et faire un `push` + +:four: La personne la plus rapide n'a, normalement, rencontré aucune difficulté (elle peut s'arrêter temporairement pour regarder ce qui va se passer chez les voisins, en respectant la distanciation sociale :mask:). Les autres voient leur `push` refusé et doivent faire un `pull`. :boom: Il y a conflit, ce qui doit être signalé par un message du type: @@ -414,11 +1061,13 @@ CONFLICT (content): Merge conflict in XXXXXX.md Automatic merge failed; fix conflicts and then commit the result. ~~~ -5. Etudier le résultat de `git status` +:five: Etudier le résultat de `git status` -6. Si vous ouvrez les fichiers incriminés, vous devriez voir des balises du type +:six: Si vous ouvrez les fichiers incriminés, vous devriez voir des balises du type -~~~markdown +```{r, echo = FALSE, results="asis", eval = FALSE} +cat( +"~~~markdown <<<<<<< HEAD this is some content to mess with content to append @@ -426,75 +1075,61 @@ content to append totally different content to merge later >>>>>>> new_branch_to_merge_later ~~~ +" +) +``` -7. Corriger à la main les fichiers en choisissant, pour chaque ligne, la version qui vous convient et en retirant les balises. Valider en faisant: +:seven: Corriger à la main les fichiers en choisissant, pour chaque ligne, la version qui vous convient et en retirant les balises. Valider en faisant: ~~~shell git add . && git commit -m "Résolution du conflit par XXXX" ~~~ -Remplacer XXX par votre nom. La balise `&&` permet d'enchaîner, en une seule ligne de code, les deux commandes. `git add .` signifie qu'on ajoute à l'*index* de `Git` toutes les modifications sur les fichiers déjà suivis. -8. Faire un push. Pour la dernière personnes, refaire les opérations 4 à 8 +Remplacer XXXX par votre nom. La balise `&&` permet d'enchaîner, en une seule ligne de code, les deux commandes. + +:eight: Faire un push. Pour la dernière personne, refaire les opérations 4 à 8 {{% /panel %}} -``` `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 `master` peut être pénible. Les branches prennent tout leur sens dans ce cas. -```{=html} -{{% panel status="exercise" title="Exercice: gestion des branches" icon="fas fa-pencil-alt" %}} -1. Le mainteneur va contribuer directement dans `master` et ne crée pas de branche. Chaque développeur crée une branche, en local nommée `contrib-XXXXX` où `XXXXX` est le prénom: - -~~~shell -git checkout -b contrib-prenom -~~~ - -2. Chaque membre du groupe crée un fichier `README.md` où il écrit une phrase sujet-verbe-complément. Le mainteneur est le seul à ajouter un titre à l'oeuvre d'art en cours de création - -3. Chacun pousse le produit de son subconscient sur le dépôt. - -4. Les développeurs ouvrent, chacun, une `pull request` sur `Github` de leur branche vers `master`. Ils lui donnent un titre explicite. +{{% panel status="exercise" title="Exercice" icon="fas fa-pencil-alt" %}} -5. Dans la discussion de chaque `pull request`, le mainteneur demande au développeur d'intégrer la première phrase qu'il a écrite. +**Exercice 11: gestion des branches** -6. Les développeurs, en local, intègrent cette modification en faisant +:one: Le mainteneur va contribuer directement dans `master` et ne crée pas de branche. Chaque développeur crée une branche, en local nommée `contrib-XXXXX` où `XXXXX` est le prénom: ~~~shell -# Pour être sûr d'être sur la branche personnelle -git checkout branche-prenom -git merge master +git checkout -b contrib-XXXXX ~~~ -Régler le conflit et valider (`add` et `commit`). Pousser le résultat. Le mainteneur choisit une des `pull request` et la valide avec l'option `squash commits`. Vérifier sur la page d'accueil le résultat. +:two: Chaque membre du groupe crée un fichier `README.md` où il écrit une phrase sujet-verbe-complément. Le mainteneur est le seul à ajouter un titre dans le README (qu'il commit dans master). -7. L'auteur de la `pull request` non validée doit à nouveau répéter l'opération 6. +:three: Chacun push le produit de son subconscient sur le dépôt. -8. Une fois le conflit de version réglé et poussé, le mainteneur valide la `pull request` selon la même procédure que précedemment. +:four: Les développeurs ouvrent, chacun, une `pull request` sur `Github` de leur branche vers `master`. Ils lui donnent un titre explicite. -9. Vérifier l'arborescence du dépôt dans `Insights > Network`. Votre arbre doit avoir une forme caractéristique de ce qu'on appelle le `Github flow`: +:five: Dans la discussion de chaque `pull request`, le mainteneur demande au développeur d'intégrer le titre qu'il a écrit. -![](https://linogaliana.gitlab.io/collaboratif/pics/03_git/flow4_discuss.png) +:six: Chaque développeur, en local, intègre cette modification en faisant -{{% /panel %}} +```{shell, eval = FALSE} +# Pour être sûr d'être sur sa propre branche +git checkout branche-XXXX +git merge master ``` -```{=html} -{{% panel status="note" title="Note" icon="fa fa-comment" %}} -Les merges vers `master` doivent impérativement passer par `Github` (ou `Gitlab`). Cela permet de garder une trace explicite de ceux-ci (par exemple [ici](https://github.com/linogaliana/python-datascientist/pulls?q=is%3Apr+is%3Aclosed)), sans avoir à chercher dans l'arborescence, parfois complexe, d'un projet. La bonne pratique veut qu'on faire un `squash commit` pour éviter une inflation du nombre de commits dans `master`: les branches ont vocation à proposer une multitude de petits commits, les modifications dans `master` doivent être simple à tracer d'où le fait de modifier des petits. +Régler le conflit et valider (`add` et `commit`). Pousser le résultat. Le mainteneur choisit une des `pull request` et la valide avec l'option `squash commits`. Vérifier sur la page d'accueil le résultat. -Le cas échéant, il est très pratique d’ajouer dans le corps du message close `#xx` où `xx` est le numéro de l’issue décrivant la fonctionnalité que la MR implémente. Lorsque la MR sera fusionnée, l’issue sera automatiquement fermée et un lien sera créé entre l’issue et la MR. Cela vous permettra de comprendre, plusieurs mois ou années plus tard comment et pourquoi telle ou telle fonctionnalité a été implémentée. +:seven: L'auteur (si 2 développeurs) ou les deux auteurs (si 3 développeurs) de la `pull request` non validée doivent à nouveau répéter l'opération 6. -L'intégration des dernières modifications de `master` 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 master**. -{{% /panel %}} +:eight: Une fois le conflit de version réglé et poussé, le mainteneur valide la `pull request` selon la même procédure que précedemment. -{{% panel status="hint" title="Hint" icon="fa fa-lightbulb" %}} -Vous devez savoir qu’il y a plusieurs méthodes de travail avec `Git` (*flow*, en anglais). Vous pourrez trouvez des dizaines d’articles et d’ouvrages sur ce sujet dont chacun prétend avoir trouvé la meilleure organisation du travail (`Git flow`, `GitHub flow`, `GitLab flow`…). Ne lisez pas trop ces livres et articles sinon vous serez perdus (un peu comme avec les magazines destinés aux jeunes parents...) +:nine: Vérifier l'arborescence du dépôt dans `Insights > Network`. Votre arbre doit avoir une forme caractéristique de ce qu'on appelle le `Github flow`: -La méthode de travail la plus simple est le *Github flow* qu'on vous a proposé d'adopter. L'arborescence est reconnaissable: des branches divergent et reviennent systématiquement vers `master`. - -Pour des projets plus complexes dans des équipes développant des applications, on pourra utiliser d'autres méthodes de travail, notamment le `Git flow`. Il n'existe pas de règles universelles pour déterminer la méthode de travail ; l'important c'est, avant tout, de se mettre d'accord sur des règles communes de travail. +![](https://linogaliana.gitlab.io/collaboratif/pics/03_git/flow4_discuss.png) {{% /panel %}} -``` \ No newline at end of file + diff --git a/content/course/git/introgit.Rmd b/content/course/git/introgit.Rmd index 705b7a2b7..d186676d2 100644 --- a/content/course/git/introgit.Rmd +++ b/content/course/git/introgit.Rmd @@ -1,4 +1,15 @@ --- +jupyter: + jupytext: + text_representation: + extension: .Rmd + format_name: rmarkdown + format_version: '1.2' + jupytext_version: 1.6.0 + kernelspec: + display_name: Python 3 + language: python + name: python3 title: "Git: un élément essentiel au quotidien" date: 2020-09-30T13:00:00Z draft: false @@ -18,32 +29,45 @@ summary: | mis en pratique dans le suivant. --- +```{r setup, include=FALSE} +dir_path <- gsub(here::here(), "..", here::here("course","git")) +knitr::knit_hooks$set( + plot = function(x, options) modif_plot(x, options, dir_path = dir_path) +) +knitr::opts_chunk$set(eval = FALSE) +``` + +```{r, echo = FALSE, results = 'asis', include = TRUE, eval = TRUE} +print_badges() +``` + Une grande partie du contenu de ce chapitre provient du cours [Travail collaboratif avec `R`](https://linogaliana.gitlab.io/collaboratif/git.html). -## Pourquoi faire du `Git` ? +# Pourquoi faire du `Git` ? -Tous les statisticiens se sont déjà demandé (ou à leurs collègues) : +Dans un projet, il est commun de se demander (ou de demander à quelqu'un) : -* quelle était la bonne version d'un programme +* quelle était la bonne version d'un programme * qui était l'auteur d'un bout de code en particulier * si un changement était important ou juste un essai -* comment fusionner des programmes +* où retrouver des traces d'un vieil essai abandonné mais potentiellement finalement prometteur +* comment fusionner des programmes écrits par plusieurs personnes * etc. -Il existe un outil informatique puissant afin de répondre à tous ces besoins : la gestion de version (*version control system* (VCS) en anglais). Ses avantages sont incontestables et permettent de facilement : +Il existe un outil informatique puissant qui répond à tous ces besoins : la gestion de version (*version control system* (VCS) en anglais). Ses avantages sont incontestables et permettent de facilement : -* enregistrer l'historique des modifications d'un ensemble de fichiers +* enregistrer l'historique des modifications d'un ensemble de fichiers * revenir à des versions précédentes d'un ou plusieurs fichiers * rechercher les modifications qui ont pu créer des erreurs * partager ses modifications et récupérer celles des autres -* proposer des modifications, les discuter, sans pour autant modifier la dernière version existante +* proposer des modifications, les discuter, sans pour autant modifier d'emblée la dernière version existante * identifier les auteurs et la date des modifications En outre, ces outils fonctionnent avec tous les langages informatiques (texte, R, Python, SAS, $\LaTeX$, Java, etc.) car reposent sur la comparaison des lignes et des caractères des programmes. -On peut ainsi résumé les principaux avantages du contrôle de version +On peut ainsi résumer les principaux avantages du contrôle de version de la manière suivante : 1. Conserver et archiver l'ensemble des versions d'un code ou d'une documentation @@ -52,27 +76,27 @@ de la manière suivante : 4. Simplifier la communication autour d'un projet -### Conserver et archiver du code +## Conserver et archiver du code -Une des principales fonctionnalités de la gestion de version est conserver l'ensemble des fichiers de façon sécurisée et de proposer un archivage structuré des codes. Les fichiers sont stockés dans un **dépôt**, qui constitue le projet +Une des principales fonctionnalités de la gestion de version est de conserver l'ensemble des fichiers de façon sécurisée et de proposer un archivage structuré des codes. Les fichiers sont stockés dans un **dépôt**, qui constitue le projet. -Tout repose dans la gestion et la présentation de l'historique des modifications. Chaque modification (ajout, suppression ou changement) sur un ou plusieurs fichiers est identifiée par son auteur, sa date et un bref descriptif^[Plus précisément, chaque modification est identifiée de manière unique par un code `SHA` auquel est associé l'auteur, l'horodatage et des méta-données (par exemple le message descriptif associé)]. Chaque changement est donc unique et aisément identifiable quand ils sont classés par ordre chronologique. Les modifications transmises au dépôt sont appelées **commit**. +Tout repose dans la gestion et la présentation de l'historique des modifications. Chaque modification (ajout, suppression ou changement) sur un ou plusieurs fichiers est identifiée par son auteur, sa date et un bref descriptif^[Plus précisément, chaque modification est identifiée de manière unique par un code `SHA` auquel est associé l'auteur, l'horodatage et des métadonnées (par exemple le message descriptif associé)]. Chaque changement est donc unique et aisément identifiable quand les modifications sont classées par ordre chronologique. Les groupes de modifications transmis au dépôt sont appelées **commit**. Avec des outils graphiques, on peut vérifier l' -[ensemble des évolutions d'un fichier (`history`)](https://github.com/linogaliana/python-datascientist/commits/master/README.md), +[ensemble des évolutions d'un fichier (`history`)](https://github.com/linogaliana/python-datascientist/commits/master/README.md), ou [l'histoire d'un dépôt](https://github.com/linogaliana/python-datascientist/commits/master). -On peut aussi +On peut aussi [se concentrer sur une modification particulière d'un fichier](https://github.com/linogaliana/python-datascientist/commit/7e5d30ae0e260f9485453b42f195b0181a53e32e#diff-04c6e90faac2675aa89e2176d2eec7d8) ou vérifier, pour un fichier, la [modification qui a entraîné l'apparition de telle ou telle ligne (`blame`)](https://github.com/linogaliana/python-datascientist/blame/master/README.md) -Sur son poste de travail, les dizaines (centaines ?) de programmes organisés à la main n'existent plus. Tout est regroupé dans un seul dossier, rassemblant les éléments du dépôt. Au sein du dépôt, tout l'historique est stocké et accessible rapidement. Si on souhaite travailler sur la dernière version des programmes (ou sur une ancienne version spécifique), il n'y a plus besoin de conserver les autres fichiers car ils sont dans l'historique du projet. Il est alors possible de choisir sur quelle version on veut travailler (la dernière commune à tout le monde, la sienne en train d'être développée, celle de l'année dernière, etc.). +Sur son poste de travail, les dizaines (voire centaines) de programmes organisés à la main n'existent plus. Tout est regroupé dans un seul dossier, rassemblant les éléments du dépôt. Au sein du dépôt, tout l'historique est stocké et accessible rapidement. Si on souhaite travailler sur la dernière version des programmes (ou sur une ancienne version spécifique), il n'y a plus besoin de conserver les autres fichiers car ils sont dans l'historique du projet. Il est alors possible de choisir sur quelle version on veut travailler (la dernière commune à tout le monde, la sienne en train d'être développée, celle de l'année dernière, etc.). -### Travailler efficacement en équipe +## Travailler efficacement en équipe -Le deuxième avantage de la gestion de version représente une amélioration notable du travail en équipe sur des codes en commun. +Le deuxième avantage de la gestion de version représente une amélioration notable du travail en équipe sur des codes en commun. - La gestion de version permet de collaborer simplement et avec méthode. De façon organisée, elle permet de: +La gestion de version permet de collaborer simplement et avec méthode. De façon organisée, elle permet de: * travailler en parallèle et fusionner facilement du code * partager une documentation des programmes grâce : @@ -83,56 +107,316 @@ correction A ces avantages s'ajoutent les fonctionalités collaboratives des sites de dépôt (les principaux étant `Github` et `Gitlab`), qui permettent d'intéragir via -des *issues*, faire des suggestions de modifications, etc. +des [*issues*](https://github.com/linogaliana/python-datascientist/issues), faire des suggestions de modifications, etc. + +L'usage individuel, c'est-à-dire seul sur son projet, permet aussi de "travailler en équipe avec soi-même" car il permet de retrouver des mois plus tard le contenu et le contexte des modifications. Cela est notamment précieux lors des changements de poste ou des travaux réguliers mais espacés dans le temps (par exemple, un mois par an chaque année). Même lorsqu'on travaille tout seul, on collabore avec un *moi* futur qui peut ne plus se souvenir de la modification des fichiers. -L'usage individuel, c'est-à-dire seul sur son projet, permet aussi de "travailler en équipe avec soi même" car il permet de retrouver des mois plus tard le contenu et le contexte des modifications. Cela est notamment précieux lors des changements de poste ou des travaux réguliers mais espacés dans le temps (par exemple, un mois par an chaque année). Même lorsqu'on travaille tout seul, on collabore avec un *moi* futur qui peut ne plus se souvenir de la modification des fichiers. -### Améliorer la qualité des codes +## Améliorer la qualité des codes -Le fonctionnement de la gestion de version, reposant sur l'archivage structuré des modifications et les commentaires les accompagnant, renforce la qualité des programmes informatiques. Ils sont plus documentés, plus riches et mieux structurés. C'est pour cette raison que le contrôle de version ne doit pas être considéré comme un outil réservé à des développeurs : toute personne travaillant sur des programmes informatiques, gagne à utiliser du contrôle de version. +Le fonctionnement de la gestion de version, reposant sur l'archivage structuré des modifications et les commentaires les accompagnant, renforce la qualité des programmes informatiques. Ils sont plus documentés, plus riches et mieux structurés. C'est pour cette raison que le contrôle de version ne doit pas être considéré comme un outil réservé à des développeurs : toute personne travaillant sur des programmes informatiques, gagne à utiliser du contrôle de version. Les services d'intégration continue permettent de faire des tests automatiques -de programmes informatiques, notamment de packages, qui renforcent la +de programmes informatiques, notamment de packages, qui renforcent la replicabilité des programmes. Mettre en place des méthodes de travail fondées -sur l'intégration continue rend les programmes plus robustes en forçant +sur l'intégration continue rend les programmes plus robustes en forçant ceux-ci à tourner sur des machines autres que celles du développeur du code. -### Simplifier la communication autour d'un projet +## Simplifier la communication autour d'un projet Les sites de dépôts `Github` et `Gitlab` permettent de faire beaucoup plus -que seulement archiver des codes. Les fonctionalités de déploiement -en continu permettent ainsi de: +que seulement archiver des codes. Les fonctionalités de déploiement +en continu permettent ainsi de : * créer des sites web pour valoriser des projets (par exemple les sites -`pkgdown` en `R`) +`readthedocs` en `python`) * déployer de la documentation en continu -* rendre visible la qualité d'un projet avec des services de *code coverage*, +* rendre visible la qualité d'un projet avec des services de *code coverage*, de tests automatiques ou d'environnements intégrés de travail (binder, etc.) qu'on rend généralement visible au moyen de badges (exemple ici {{< githubrepo >}}) -### Comment faire du contrôle de version ? +# Comment faire du contrôle de version ? -Il existe plusieurs manières d'utiliser le contrôle de version : +Il existe plusieurs manières d'utiliser le contrôle de version : * en ligne de commande, via [git bash](https://gitforwindows.org/), par exemple ; * avec une interface graphique spécialisée, par exemple [tortoise git](https://tortoisegit.org/) ou [GitHub Desktop](https://desktop.github.com/) ; -* avec un logiciel de développement : la plupart des logiciels de développement ([RStudio](https://rstudio.com/) pour `R`, [PyCharm](https://www.jetbrains.com/fr-fr/pycharm/) ou [jupyter](https://jupyter.org/) pour `python`, [atom](https://atom.io/), etc.) proposent tous des modules graphiques facilitant l'usage de `git` dont les fonctionnalités sont très proches. +* avec un logiciel de développement : la plupart des logiciels de développement ([RStudio](https://www.book.utilitr.org/git.html) pour `R`, [PyCharm](https://www.jetbrains.com/help/pycharm/using-git-integration.html), [jupyter](https://annefou.github.io/jupyter_publish/02-git/index.html) ou encore +[visual studio (extension GitLens)](https://marketplace.visualstudio.com/items?itemName=eamodio.gitlens) +pour `python`, proposent tous des modules graphiques facilitant l'usage de `Git`. + -```{=html} -{{% panel status="tip" title="Tip" icon="fa fa-lightbulb" %}} `Git` a été conçu, initialement pour la ligne de commande. Il existe néanmoins des interfaces graphiques performantes et pratiques, notamment lorsqu'on désire comparer deux versions d'un même fichier côte à côte. Ces interfaces graphiques couvrent la majorité des besoins quotidiens. Néanmoins, pour certaines tâches, il faut nécessairement passer par la ligne de commande. -{{% /panel %}} + + +En résumé, + +| | Avant | Gestion de version | +|:---------------------|----------------------:|---------------------:| +|Archivage | à la main | automatique | +|Envoi de modification | mail, dossier partagé | code partagé | +|Fusion de code | copié-collé | simple, sûr | +|Versions du modèle | dossiers archivés | historique riche | +|Gestion des erreurs | à la main | méthodique | + + +# Quelques bases sur `Git` + +## Copies de travail et dépôt collectif + +`Git` est un système décentralisé et asynchrone de gestion de version. +Cela signifie que: + +* Chaque membre d'un projet travail sur une copie locale du dépôt +(système *decentralisé*). Cette copie de travail s'appelle un `clone`. +Cela signifie qu'on n'a pas une cohérence en continue de notre version +de travail avec le dépôt ; on peut très bien ne jamais vouloir les +mettre en cohérence (par exemple, si on teste une piste qui s'avère +infructueuse) ; +* C'est lorsqu'on propose la publication de modifications sur le dépôt +collectif qu'on doit s'assurer de la cohérence avec la version disponible +en ligne (système *asynchrone*). + +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 +qu'on utilise pour faire évoluer un projet: + +![](https://www.book.utilitr.org/pics/git/gitlab.png) + +Il est tout à fait possible de faire du contrôle de version sans +mettre en place de dépôt distant. Cependant, + +* c'est dangereux puisque le dépôt distant fait office de sauvegarde +d'un projet. Sans dépôt distant, on peut tout perdre en cas de problème +sur la copie locale de travail ; +* c'est désirer être moins efficace car, comme nous allons le montrer, les +fonctionalités des plateformes `Github` et `Gitlab` sont également très +bénéfiques lorsqu'on travaille tout seul. + +## Principe + + +Les trois manipulations les plus courantes sont les suivantes et représentées sur le diagramme ci-après : + +* `commit` : je valide les modifications que j'ai faites en local avec un message qui les expliquent +* `pull` : je récupère la dernière version des codes du dépôt distant +* `push` : je transmets mes modifications validées au dépôt distant + +```{r, echo = FALSE} +knitr::include_graphics("https://gitlab.com/linogaliana/collaboratif/-/raw/master/pics/03_git/push_pull_Drees.png") +``` + +Les deux dernières manipulations correspondent aux interactions (notamment +la mise en cohérence) avec +le dépôt commun alors que la première manipulation `commit` correspond à +la modification des fichiers faite pour faire évoluer un projet. + +De manière plus précise, il y a trois étapes avant d'envoyer les modifications validées (`commit`) au dépôt. Elles se définissent en fonction des commandes qui permet de les appliquer quand Git est utilisé en ligne de commandes : + +* `diff` : inspection des modifications. Cela permet de comparer les fichiers modifiés et de distinguer les fichiers ajoutés ou supprimés +* `staging area` : sélection des modifications. +* `commit` : validation des modifications sélectionnées (avec commentaire). + +```{r, echo=FALSE} +knitr::include_graphics("https://gitlab.com/linogaliana/collaboratif/-/raw/master/pics/03_git/trois_%C3%A9tats_fichier.png") ``` -## Git: le B.A-BA +Lors des étapes de `push` et `pull`, des **conflits** peuvent apparaître, par exemple lorsque deux personnes ont modifié le même programme simultanément. Le terme conflit peut faire peur mais en fait c'est +l'un des apports principaux de `Git` que de faciliter énormément la gestion +de versions différentes. Les exercices du chapitre suivant l'illustreront. + +## Les branches + + +C'est une des fonctionnalités les plus pratiques de la gestion de version. +La création de `branches` dans un projet (qui devient ainsi un arbre) +permet de développer en parallèle des correctifs ou une nouvelle fonctionnalité +sans modifier le dépôt commun. + +Cela permet de séparer le nouveau développement et de faire cohabiter plusieurs versions, pouvant évoluer séparément ou pouvant être facilement rassemblées. Git est optimisé pour le travail sur les branches. + +Dans un projet collaboratif, une branche dite **master** joue le rôle du tronc. +C'est autour d'elle que vont pousser ou se greffer les branches. +L'un des avantages de `Git` est qu'on peut toujours revenir en arrière. Ce +filet de sécurité permet d'oser des expérimentations, y compris au sein +d'une branche. Il faut être prêt à aller dans la ligne de commande pour cela +mais c'est extrêmement confortable. + +{{% panel status="hint" title="Hint" icon="fa fa-lightbulb" %}} +Comment nommer les branches ? Là encore, il y a énormément de conventions différentes. Une fréquemment observée est : + +- pour les nouvelles fonctionnalités : `feature/nouvelle-fonctionnalite` où `nouvelle-fontionnalite` est un nom court résumant la fonctionnalité +- pour les corrections de bug : `issue-num` où `num` est le numéro de l'issue + +N'hésitez pas à aller encore plus loin dans la normalisation ! +{{% /panel %}} + + + +# Conseil de praticien: ne pas négliger le `.gitignore` + +Un fichier à ne pas négliger est le `.gitignore`. Il s'agit d'un garde-fou car tous fichiers (notamment des +données, potentiellement volumineuses ou confidentielles) n'ont pas vocation +à être partagés. + +Le site [gitignore.io](https://www.toptal.com/developers/gitignore) est très pratique. Le fichier +suivant est par exemple proposé pour les utilisateurs de `Python`, auquel on peut ajouter +quelques lignes adaptées aux utilisateurs de données: + +~~~markdown +*.html +*.pdf +*.csv +*.tsv +*.json +*.xml +*.shp +*.xls +*.xlsx + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ +~~~ diff --git a/content/course/manipulation/04a_webscraping_TP.Rmd b/content/course/manipulation/04a_webscraping_TP.Rmd index 9a4f2d2e4..269995693 100644 --- a/content/course/manipulation/04a_webscraping_TP.Rmd +++ b/content/course/manipulation/04a_webscraping_TP.Rmd @@ -158,7 +158,7 @@ On dira que l'élément ``
`` est le parent de l'élément ``

`` tandis qu :comment: *Mais pourquoi apprendre ça pour "scraper", me direz-vous ?* -Parce que, pour bien récupérer les informations d'un site internet, il faut pouvoir comprendre sa structure et donc son code HTML. Les fonctions python qui servent au scrapping sont principalement construites pour vous permettre de naviguer entre les balises. +Parce que, pour bien récupérer les informations d'un site internet, il faut pouvoir comprendre sa structure et donc son code HTML. Les fonctions python qui servent au scraping sont principalement construites pour vous permettre de naviguer entre les balises. # Scraper avec python: le package `BeautifulSoup` @@ -402,8 +402,6 @@ data_participants.head() Essayez de comprendre pas à pas ce qui est fait dans les étapes qui suivent (la récupération d'informations supplémentaires en naviguant dans les pages des différents clubs). ```{python} -#| include: false - import urllib import pandas as pd import bs4 @@ -520,6 +518,7 @@ va utilise `folium` pour celle-ci, qui est présenté dans la partie [visualisation](#cartotp) ```{python folium1, echo = TRUE, results = "hide", message = FALSE, warning = FALSE} +#!pip install geopandas import geopandas as gpd from pathlib import Path import folium @@ -537,11 +536,15 @@ m = folium.Map(location = center, tiles='Stamen Toner') # I can add marker one by one on the map for i in range(0,len(gdf)): - folium.Marker([gdf.iloc[i]['latitude'], gdf.iloc[i]['longitude']], popup=gdf.iloc[i]['stade']).add_to(m) + folium.Marker([gdf.iloc[i]['latitude'], gdf.iloc[i]['longitude']], popup=gdf.iloc[i]['stade']).add_to(m) m.fit_bounds([sw, ne]) ``` +```{python, eval = FALSE} +# Afficher la carte +m +``` ```{python, eval = TRUE, include = FALSE} map = m._repr_html_() @@ -847,7 +850,7 @@ def get_cara_pokemon(pokemon_name): cells = cells.replace('\t','').replace('\n',' ') data[column] = cells data['name'] = pokemon_name - return dict(data) + return data ``` Vous devriez obtenir une liste de caractéristiques proche de celle-ci @@ -965,12 +968,9 @@ L'installation de `selenium` nécessite d'avoir chromium qui est un navigateur Google Chrome minimaliste. La version de [chromedriver](https://sites.google.com/a/chromium.org/chromedriver/) doit être ``>= 2.36`` et dépend de la version de Chrome que vous avez sur votre poste. -```{shell, eval = FALSE} -# #!pip install selenium -``` - - ```{python, eval = FALSE} +!pip install selenium + # Sur votre poste # télécharger le chrome driver https://chromedriver.storage.googleapis.com/index.html?path=85.0.4183.83/ diff --git a/content/course/manipulation/04b_regex_TP.Rmd b/content/course/manipulation/04b_regex_TP.Rmd index 23f590af6..2782ca042 100644 --- a/content/course/manipulation/04b_regex_TP.Rmd +++ b/content/course/manipulation/04b_regex_TP.Rmd @@ -39,6 +39,8 @@ knitr::knit_hooks$set( print_badges() ``` +Ce TD est directement issu du contenu de [Xavier Dupré](http://www.xavierdupre.fr/app/teachpyx/helpsphinx/c_regex/regex.html), +l'ancien professeur de ce cours. Il sera mis-à-jour dans le futur. Chercher un mot dans un texte est une tâche facile, c'est l'objectif de la méthode `find()` attachée aux chaînes de caractères, étudiée dans la partie webscraping. Elle suffit également lorsque l'on cherche un mot au pluriel ou au singulier, mais il faut dans ce cas l'appeler au moins deux fois. @@ -179,7 +181,7 @@ print(liste_emails) {{% panel status="exercise" title="Exercice" icon="fas fa-pencil-alt" %}} -**Exercice 1: Application directe ** +**Exercice 1: Application directe** Recherchez les dates présentes dans la phrase suivante. @@ -211,7 +213,7 @@ print(expression.findall(texte2)) {{% panel status="exercise" title="Exercice" icon="fas fa-pencil-alt" %}} -**Exercice 2: Nettoyer une colonne de date de publication ** +**Exercice 2: Nettoyer une colonne de date de publication** L'objectif général de l'exercice est de nettoyer des colonnes d'un DataFrame en utilisant des expressions régulières. diff --git a/content/course/manipulation/04c_API_TP.Rmd b/content/course/manipulation/04c_API_TP.Rmd index 0f55dad50..ab825cb58 100644 --- a/content/course/manipulation/04c_API_TP.Rmd +++ b/content/course/manipulation/04c_API_TP.Rmd @@ -33,6 +33,9 @@ summary: | ```{r setup, include=FALSE} dir_path <- gsub(here::here(), "..", here::here("course","manipulation")) +knitr::knit_hooks$set( + plot = function(x, options) modif_plot(x, options, dir_path = dir_path) +) ``` @@ -40,245 +43,414 @@ dir_path <- gsub(here::here(), "..", here::here("course","manipulation")) print_badges() ``` -Petite revue d'[API REST](https://fr.wikipedia.org/wiki/Representational_state_transfer). +# Introduction : Qu'est-ce qu'une API ? - -## Définition : +## Définition -API, à part que ce mot qui vaut 5 au scrabble, c'est quoi au juste ? +Pour expliquer le principe d'une API, je vais reprendre le début de +la fiche dédiée dans la documentation collaborative +[utilitR](https://www.book.utilitr.org/api.html) que je recommande de lire : +> Une *Application Programming Interface* (ou API) est une interface de programmation qui permet d’utiliser une application existante pour restituer des données. Le terme d’API peut être paraître intimidant, mais il s’agit simplement d’une façon de mettre à disposition des données : plutôt que de laisser l’utilisateur consulter directement des bases de données (souvent volumineuses et complexes), l’API lui propose de formuler une requête qui est traitée par le serveur hébergeant la base de données, puis de recevoir des données en réponse à sa requête. +> +> D’un point de vue informatique, une API est une porte d’entrée clairement identifiée par laquelle un logiciel offre des services à d’autres logiciels (ou utilisateurs). L’objectif d’une API est de fournir un point d’accès à une fonctionnalité qui soit facile à utiliser et qui masque les détails de la mise en oeuvre. Par exemple, l’API Sirene permet de récupérer la raison sociale d’une entreprise à partir de son identifiant Siren en interrogeant le référentiel disponible sur Internet directement depuis un script R, sans avoir à connaître tous les détails du répertoire Sirene. +> +> À l’Insee comme ailleurs, la connexion entre les bases de données pour les nouveaux projets tend à se réaliser par des API. L’accès à des données par des API devient ainsi de plus en plus commun et est amené à devenir une compétence de base de tout utilisateur de données. +> +> [`utilitR`](https://www.book.utilitr.org/api.html) -API signifie Application Programming Interface. Le mot le plus important est “interface”, et c’est le mot le plus simple, car nous utilisons tous des interfaces. +## Avantages des API -Bon, et une interface ? +A nouveau, citons la documentation [utilitR](https://www.book.utilitr.org/api.html) -> Définition Larousse : "Une interface est un dispositif qui permet des échanges et interactions entre différents acteurs" +Les API présentent de multiples avantages : -Pour faire simple, une API est un moyen efficace de faire communiquer entre elles deux applications : concrètement, un fournisseur de service met à disposition des développeurs une interface codifiée, qui leur permet d'obtenir des informations à partir de requêtes. +> * Les API rendent les programmes plus reproductibles. En effet, grâce aux API, il est possible de mettre à jour facilement les données utilisées par un programme si celles-ci évoluent. Cette flexibilité accrue pour l’utilisateur évite au producteur de données d’avoir à réaliser de multiples extractions, et réduit le problème de la coexistence de versions différentes des données. +> * Grâce aux API, l’utilisateur peut extraire facilement une petite partie d’une base de données plus conséquente. +> * Les API permettent de mettre à disposition des données tout en limitant le nombre de personnes ayant accès aux bases de données elles-mêmes. +> * Grâce aux API, il est possible de proposer des services sur mesure pour les utilisateurs (par exemple, un accès spécifique pour les gros utilisateurs). +> +> [`utilitR`](https://www.book.utilitr.org/api.html) -Sans rentrer dans le détail technique, le dialogue ressemble à : "envoie moi ton adresse sous la forme X = rue, Y = Ville, Z = Pays" et moi, en retour, je t'enverrai le code à afficher sur ton site pour avoir la carte interactive. +## Utilisation des API +Citons encore une fois +la documentation [`utilitR`](https://www.book.utilitr.org/api.html) +> Une API peut souvent être utilisée de deux façons : par une interface Web, et par l’intermédiaire d’un logiciel (R, Python…). Par ailleurs, les API peuvent être proposées avec un niveau de liberté variable pour l’utilisateur : +> +> * soit en libre accès (l’utilisation n’est pas contrôlée et l’utilisateur peut utiliser le service comme bon lui semble) ; +> * soit via la génération d’un compte et d’un jeton d’accès qui permettent de sécuriser l’utilisation de l’API et de limiter le nombre de requêtes. +> +> [`utilitR`](https://www.book.utilitr.org/api.html) - +De nombreuses API nécessitent une authentification, c'est-à-dire un +compte utilisateur afin de pouvoir accéder aux données. +Dans un premier temps, +nous regarderons exclusivement les API ouvertes sans restriction d'accès. +Certains exercices et exemples permettront néanmoins d'essayer des API +avec restrictions d'accès. - -## Les API qui existent +# Requêter une API +## Principe général -De plus en plus de sites mettent à disposition des développeurs et autres curieux des API. +> L’utilisation de l’interface Web est utile dans une démarche exploratoire mais trouve rapidement ses limites, notamment lorsqu’on consulte régulièrement l’API. L’utilisateur va rapidement se rendre compte qu’il est beaucoup plus commode d’utiliser une API via un logiciel de traitement pour automatiser la consultation ou pour réaliser du téléchargement de masse. De plus, l’interface Web n’existe pas systématiquement pour toutes les API. +> +> Le mode principal de consultation d’une API consiste à adresser une requête à cette API via un logiciel adapté (R, Python, Java…). Comme pour l’utilisation d’une fonction, l’appel d’une API comprend des paramètres qui sont détaillées dans la documentation de l’API. +> +> [`utilitR`](https://www.book.utilitr.org/api.html) -Pour en citer quelques-uns : -- Twitter : https://dev.twitter.com/rest/public -- Facebook : https://developers.facebook.com/ -- Instagram : https://www.instagram.com/developer/ -- Spotify : https://developer.spotify.com/web-api/ +Voici les éléments importants à avoir en tête sur les requêtes (j'emprunte encore +à [`utilitR`](https://www.book.utilitr.org/api.html)): +* Le __point d’entrée__ d’un service offert par une API se présente sous la forme d’une URL (adresse web). Chaque service proposé par une API a sa propre URL. Par exemple, dans le cas de l’OpenFood Facts, +l'URL à utiliser pour obtenir des informations sur un produit particulier (l'identifiant `737628064502`) estg https://world.openfoodfacts.org/api/v0/product/737628064502.json +* Cette URL doit être complétée avec différents paramètres qui précisent la requête (par exemple l’identifiant Siren). Ces paramètres viennent s’ajouter à l’URL, souvent à la suite de `?`. Chaque service proposé par une API a ses propres paramètres, détaillés dans la documentation. +* Lorsque l’utilisateur soumet sa requête, l’API lui renvoie une réponse structurée contenant l’ensemble des informations demandées. Le résultat envoyé par une API est majoritairement aux formats JSON ou XML (deux formats dans lesquels les informations sont hiérarchisées de manière emboitée). Plus rarement, certains services proposent une information sous forme plate (de type csv). -Ou encore : +Du fait de la dimension hiérarchique des formats JSON ou XML, le résultat n’est pas toujours facile à récupérer mais +`python` propose d'excellents outils pour cela (meilleurs que ceux de `R`). Certains packages, notamment `json`, facilitent l’extraction de champs d’une sortie d’API. Dans certains cas, des packages spécifiques à une API ont été créés pour simplifier l’écriture d’une requête ou la récupération du résultat. Par exemple, le package +[pynsee](https://github.com/InseeFrLab/Py-Insee-Data/tree/master/pynsee) +propose des options qui seront retranscrites automatiquement dans l'URL de +requête pour faciliter le travail sur les données Insee. -- Pole Emploi : https://www.emploi-store-dev.fr/portail-developpeur-cms/home.html -- SNCF : https://data.sncf.com/api -- Banque Mondiale : https://datahelpdesk.worldbank.org/knowledgebase/topics/125589 - -pour beaucoup d'entre elles, il faut créer un compte utilisateur afin de pouvoir accéder aux données (c'est notamment le cas pour les réseaux sociaux), nous regarderons en cours seulement les API ouvertes sans restriction d'accès. - - -## Comment parler à une API ? +## Exemple avec l'API de la Banque Mondiale -La plupart des API donnent des exemples par communiquer avec les données présentes sur le site. +Avec l'API de la Banque mondiale, voici comme s'écrit une requête : -Simplement, il faut trouver l'url qui renvoit les données que vous souhaitez avoir +> http://api.worldbank.org/v2/countries?incomeLevel=LMC -Par exemple, avec l'API de la Banque mondiale, voici comme s'écrit une requête pour les données de la Banque Mondiale : +1. Le point d'entrée est l'URL +2. Un filtre est appliqué sur les pays (`countries?`) afin de ne conserver +que celles telles que `incomeLevel=LMC` (*"Lower middle income"*) -http://api.worldbank.org/v2/countries?incomeLevel=LMC&format=json +En cliquant sur le lien, le site renvoie des données en XML, +qui ressemblent pas mal à ce qu'on a vu plus tôt avec le scraping : une structure avec des balises qui s'ouvrent et qui se ferment. -Avec cette url, on demande la liste des pays dont le niveau de revenus est LMC, c'est à dire "Lower middle income". -En cliquant sur le lien, le site renvoit des données en XML, qui ressemblent pas mal à ce qu'on a vu plus tôt avec le scraping : une structure avec des balises qui s'ouvrent et qui se ferment. +Pour obtenir la même information en `Python`, il faut revenir aux fondamentaux : on va avoir besoin du module `requests`. Suivant les API, nous avons soit besoin de rien de plus si nous parvenons directement à obtenir un json, soit devoir utiliser un *parser* comme `BeautifulSoup` dans le cas contraire. -```{python active="", eval=FALSE} - - - AM - Armenia - Europe & Central Asia - Europe & Central Asia (excluding high income) - Lower middle income - IBRD - Yerevan - 44.509 - 40.1596 - +Avec l'API de la banque mondiale, on va utiliser le module `requests` et sa méthode `get` : on lui donne l'url de l'API qui nous intéresse, on lui demande d'en faire un json et le tour est *en apparence* joué. - - BD - Bangladesh - South Asia - South Asia - Lower middle income - IDA - Dhaka - 90.4113 - 23.7055 - - -..... +```{python} +import requests +req = requests.get('http://api.worldbank.org/v2/countries?incomeLevel=LMC') +req ``` - Quand on regare de plus près, on voit que les informations suivantes apparaissent - -Code du pays | Nom du pays | Région | Classification en termes de revenus | Les types de prêt pour ces pays | La capitale | Longitude | Latitude - +Prenons par exemple les 1000 premiers caractères du résultat: ----------------- - - AM - Armenia - Europe & Central Asia - Europe & Central Asia (excluding high income) - Lower middle income - IBRD - Yerevan - 44.509 - 40.1596 - +```{python, results = "asis"} +print(req.content[:1000]) +``` - - BD - Bangladesh - South Asia - South Asia - Lower middle income - IDA - Dhaka - 90.4113 - 23.7055 - +Quand on regarde de plus près, on voit que les informations suivantes apparaissent: + +* Code du pays +* Nom du pays +* Région +* Classification en termes de revenus +* Les types de prêt pour ces pays +* La capitale +* Longitude +* Latitude -En utilisant cette url ci : http://api.worldbank.org/v2/countries?incomeLevel=LMC&format=json, on obtient directement un json, qui est finalement presque comme un dictionnaire en python. +Le format XML est fortement balisé, ce qui n'est pas très pratique. +En utilisant désormais un autre URL, on obtient un JSON, plus pratique pour travailler : +> http://api.worldbank.org/v2/countries?incomeLevel=LMC&format=json -Rien de plus simple donc pour demander quelque chose à une API, il suffit d'avoir la bonne url. +```{python} +import requests +import pandas as pd +req = requests.get('http://api.worldbank.org/v2/countries?incomeLevel=LMC&format=json') +``` -## Et Python : comment il s'adresse aux API ? +A nouveau, les premiers caractères sont les suivants: -C'est là qu'on revient aux fondamentaux : on va avoir besoin du module requests de Python et suivant les API, un parser comme BeautifulSoup ou rien si on réussit à obtenir un json. +```{python, results = "asis"} +print(req.content[:1000]) +``` -On va utiliser le module requests et sa méthode get : on lui donne l'url de l'API qui nous intéresse, on lui demande d'en faire un json et le tour est joué ! +Cela ressemble déjà plus à un dictionnaire `Python`[^1]. +[^1]: Le JSON est un format très apprécié dans le domaine du *big data* +car il permet de stocker de manière intelligente des données +de structures diverses. Il +s'agit d'un des formats privilégiés du paradigme No-SQL pour lequel +cet [excellent cours](http://b3d.bdpedia.fr/) propose plus de détails -### Banque Mondiale : données économiques par pays +Ici, il n'est même pas nécessaire en première approche +d'utiliser le package `json`, l'information +étant déjà tabulée dans l'écho renvoyé (on a la même information pour tous les pays): ```{python} -import requests -data_json = requests.get("http://api.worldbank.org/v2/countries?incomeLevel=LMC&format=json").json() -data_json +wb = req.json() +wb = pd.json_normalize(wb[1]) +wb.head(5) ``` +Cependant, si on regarde la dimension de l'objet obtenu, on obtient un +chiffre rond (50 lignes). Ceci est suspect et un petit tour dans la +documentation de l'API nous apprendrait que c'est le nombre maximal de +retour possible. Il faut donc faire attention à la documentation et +ajouter un paramètre `page=2` pour rattraper les derniers échos: + ```{python} -data_json[0] -# On voit qu'il y a nous manque des informations : -# il y a un total de 52 éléments -data_json_page_2 = requests.get("http://api.worldbank.org/v2/countries?incomeLevel=LMC&format=json&page=2").json() -data_json_page_2 +wb2 = pd.json_normalize( + requests.get("http://api.worldbank.org/v2/countries?incomeLevel=LMC&format=json&page=2").json()[1] + ) +pd.concat([wb, wb2]) ``` +Si on regarde l'information présente dans le DataFrame, on voit qu'elle se +présente sous forme `lendingType.value`. C'est parce que `pandas` a +concaténé les différents niveaux de notre dictionnaire. Si on désire +s'en assurer, on peut regarder sur un exemple: + ```{python} -# pour obtenir une observation -# on voit dans l'objet que l'élément 0 correspond à des informations sur les pages , pour avoir les informations des pays, -# il faudra voir à partir de l'élément 1 de la liste qui est également une liste -data_json[1][0] +req.json()[1][0]['incomeLevel']['value'] == wb.loc[0, 'incomeLevel.value'] ``` -### DVF : les transactions immobilières en France +## Un catalogue incomplet d'API existantes -Le site DVF (demandes de valeurs foncières),permet de visualiation toutes les données relatives mutations à titre onéreux réalisées les 5 dernières années (pour faire simple les ventes de maison ou d'apparement depuis 2015). -https://app.dvf.etalab.gouv.fr/ +De plus en plus de sites mettent des API à disposition des développeurs et autres curieux. -Ce site est très complet quand il s'agit de connaitre le prix moyen au mètre carré d'un quartier ou de comparer des régions entre elles. +Pour en citer quelques-unes très connues : -L'API DVF a été réalisée par Christian Quest, Son code est disponible sur https://github.com/cquest/dvf_as_api +- Twitter : https://dev.twitter.com/rest/public +- Facebook : https://developers.facebook.com/ +- Instagram : https://www.instagram.com/developer/ +- Spotify : https://developer.spotify.com/web-api/ +Cependant, il est intéressant de ne pas se restreindre à celles-ci. Beaucoup +de producteurs de données, privés comme publics, mettent à disposition +leurs données sous forme d'API +- Pole Emploi : https://www.emploi-store-dev.fr/portail-developpeur-cms/home.html +- SNCF : https://data.sncf.com/api +- Banque Mondiale : https://datahelpdesk.worldbank.org/knowledgebase/topics/125589 +- Insee: https://api.insee.fr/catalogue/ et [`pynsee`](https://github.com/InseeFrLab/Py-Insee-Data/tree/master/pynsee) + + +# L'API DVF : accéder à des données de transactions immobilières simplement + +Le site `DVF` (demandes de valeurs foncières) permet de visualiser toutes les données relatives aux mutations à titre onéreux (ventes de maisons, appartements, garages...) réalisées durant les 5 dernières années + +Un site de visualisation est disponible sur + +Ce site est très complet quand il s'agit de connaître le prix moyen au mètre +carré d'un quartier ou de comparer des régions entre elles. +L'API DVF permet d'aller plus loin afin de récupérer les résultats dans +un logiciel de traitement de données. Elle a été réalisée par +[Christian Quest](https://github.com/cquest) et le code +source est disponible sur Github + +Les critères de recherche sont les suivants : +- `code_commune` = code INSEE de la commune (ex: 94068) +- `section` = section cadastrale (ex: 94068000CQ) +- `numero_plan` = identifiant de la parcelle, (ex: 94068000CQ0110) +- `lat` + `lon` + `dist` (optionnel): pour une recherche géographique, dist est par défaut un rayon de 500m +- `code_postal` +Les filtres de sélection complémentaires : +- `nature_mutation` (Vente, etc) +- `type_local` (Maison, Appartement, Local, Dépendance) + + +{{% panel status="exercise" title="Exploiter l'API DVF" icon="fas fa-pencil-alt" %}} -Un exemple : on recherche toutes les transactions existantes dans DVF à Plogoff (code commune 29168, en Bretagne) +:one: +Rechercher toutes les transactions existantes dans DVF à Plogoff (code commune `29168`, en Bretagne). +Afficher les clés du JSON et en déduire le nombre de transactions répertoriées. ```{python} +#| echo: false +#| include: false + +# Question 1 data_immo = requests.get("http://api.cquest.org/dvf?code_commune=29168").json() +data_immo.keys() +ventes = pd.json_normalize(data_immo["resultats"]) ``` + +:two: +N'afficher que les transactions portant sur des maisons. Le résultat devrait +ressembler au DataFrame suivant: + ```{python} -data_immo['resultats'][20] +#| echo: false + +# Question 2 +maisons = requests.get("http://api.cquest.org/dvf?code_commune=29168&type_local=Maison").json() +pd.json_normalize(maisons["resultats"]) ``` -Les critères de recherche sont les suivants : -- code_commune = code INSEE de la commune (ex: 94068) -- section = section cadastrale (ex: 94068000CQ) -- numero_plan = identifiant de la parcelle, (ex: 94068000CQ0110) -- lat + lon + dist (optionnel): pour une recherche géographique, dist est par défaut un rayon de 500m -- code_postal -Les filtres de sélection complémentaires : -- nature_mutation (Vente, etc) -- type_local (Maison, Appartement, Local, Dépendance) +:three: Utiliser l'[API geo](https://api.gouv.fr/documentation/api-geo) pour +récupérer le découpage communal de la ville de Plogoff +```{python} +#| echo: false -Par exemple si seules les maisons nous intéressent +import geopandas as gpd +plgf = gpd.read_file("https://geo.api.gouv.fr/communes/29168?fields=nom,code,codesPostaux,codeDepartement,codeRegion,population&format=geojson&geometry=contour") +``` + +:four: +Représenter l'histogramme des prix de vente + +```{python, results = "hide"} +p = ventes["valeur_fonciere"].plot(kind = "hist") +p +``` + +N'hésitez pas à aller plus loin en jouant sur des variables de +groupes par exemple + +:five: +On va faire une carte des ventes en affichant le prix de l'achat. + +Supposons que le DataFrame des ventes s'appelle `ventes`. Il faut d'abord le +convertir +en objet `geopandas`. ```{python} -data_immo = requests.get("http://api.cquest.org/dvf?code_commune=29168&type_local=Maison").json() +ventes = ventes.dropna(subset = ['lat','lon']) +ventes = gpd.GeoDataFrame(ventes, geometry=gpd.points_from_xy(ventes.lon, ventes.lat)) +ventes ``` +Avant de faire une carte, on va convertir +les limites de la commune de Plogoff en geoJSON pour faciliter +sa représentation avec `folium` +([voir la doc `geopandas` à ce propos](https://geopandas.readthedocs.io/en/latest/gallery/polygon_plotting_with_folium.html#Add-polygons-to-map)): + ```{python} -data_immo['resultats'][0] +geo_j = plgf.to_json() +``` + +Pour représenter graphiquement, on peut utiliser le code suivant (essayez de +le comprendre et pas uniquement de l'exécuter). + +```{python, results = "hide"} +import folium +import numpy as np + +ventes['map_color'] = pd.qcut(ventes['valeur_fonciere'], [0,0.8,1], labels = ['lightblue','red']) +ventes['icon'] = np.where(ventes['type_local']== 'Maison', "home", "") +ventes['num_voie_clean'] = np.where(ventes['numero_voie'].isnull(), "", ventes['numero_voie']) +ventes['text'] = ventes.apply(lambda s: "Adresse: {num} {voie}
Vente en {annee}
Prix {prix:.0f} €".format( + num = s['num_voie_clean'], + voie = s["voie"], + annee = s['date_mutation'].split("-")[0], + prix = s["valeur_fonciere"]), + axis=1) + +center = ventes[['lat', 'lon']].mean().values.tolist() +sw = ventes[['lat', 'lon']].min().values.tolist() +ne = ventes[['lat', 'lon']].max().values.tolist() + +m = folium.Map(location = center, tiles='Stamen Toner') + +# I can add marker one by one on the map +for i in range(0,len(ventes)): + folium.Marker([ventes.iloc[i]['lat'], ventes.iloc[i]['lon']], + popup=ventes.iloc[i]['text'], + icon=folium.Icon(color=ventes.iloc[i]['map_color'], icon=ventes.iloc[i]['icon'])).add_to(m) + +m.fit_bounds([sw, ne]) ``` +```{python, eval = FALSE} +# Afficher la carte +m +``` + +```{python, include = FALSE} +map = m._repr_html_() +``` + +```{r, echo = FALSE, results = 'asis'} +cat(py$map) +``` + +{{% /panel %}} + + + + # Exercices supplémentaires +{{% panel status="exercise" title="Exercise" icon="fas fa-pencil-alt" %}} -## Retrouver des produits dans l'openfood facts :pizza: +**Exercice 1 : Retrouver des produits dans l'openfood facts :pizza:** Voici une liste de code-barres: `3274080005003, 5449000000996, 8002270014901, 3228857000906, 3017620421006, 8712100325953` Utiliser l'[API d'openfoodfacts](https://world.openfoodfacts.org/data) -(l'API, pas depuis le CSV) +(l'API, pas depuis le CSV !) pour retrouver les produits correspondant et leurs caractéristiques nutritionnelles. Le panier paraît-il équilibré ? :chocolate_bar: +Pour vous aidez, vous pouvez regarder une exemple de structure du json ici : https://world.openfoodfacts.org/api/v0/product/3274080005003.json en particulier la catégorie `nutriments`. + ```{r, include = FALSE} library(reticulate) ``` ```{python} +#| include: false + import json import requests import pandas as pd +``` + + +```{python} +#| include: false +# Paramètres nécessaires df = pd.DataFrame([3274080005003, 5449000000996, 8002270014901, 3228857000906, 3017620421006, 8712100325953], columns = ['code_ean']) nutri = ['energy_100g', 'nutriscore_grade', 'nova_group', 'fat_100g', 'saturated-fat_100g', 'carbohydrates_100g', 'sugars_100g', 'salt_100g', 'fiber_100g', 'proteins_100g', 'calcium_100g', 'iron_100g', 'sodium_100g', 'cholesterol_100g'] cols_api = ['code', 'product_name', 'categories', 'categories_tags'] + ["nutriments.{}".format(i) for i in nutri] +``` + +```{python} +#| include: false + def get_products_api(barcode, col = cols_api): - res = requests.get("https://world.openfoodfacts.org/api/v0/product/{}.json".format(str(barcode))) + url = "https://world.openfoodfacts.org/api/v0/product/{}.json".format(str(barcode)) + #print(url) + res = requests.get(url) results = res.json() product = results["product"] openfood = pd.json_normalize(product) openfood = openfood[list(set(col) & set(openfood.columns))] return openfood +# Exemple +get_products_api(3274080005003, col=["code","nutriments.fat_100g"]) +``` + + +```{python} +#| include: false + openfood = [get_products_api(barcode) for barcode in df['code_ean'].dropna().astype(str).unique()] openfood = pd.concat(openfood) openfood.head(10) @@ -287,10 +459,15 @@ openfood.head(10) Récupérer l'URL d'une des images et l'afficher dans votre navigateur. Par exemple, celle-ci: -```{python, echo = FALSE} + +```{python} +#| echo: false + url_image = get_products_api(5449000000996, col = ["image_front_small_url"])["image_front_small_url"].iloc[0] ``` ```{r, echo = FALSE} knitr::include_graphics(py$url_image) ``` + +{{% /panel %}}