Activité pratique

Durée2h30

Consignes globales

Cette activité se déroule en deux parties. La première se fait seul(e), la seconde en binôme (un trinôme au maximum si le compte est impair).

Le fil conducteur est un projet uv, comme celui que vous avez créé en séance 2. Ce n’est pas un détail de mise en scène : uv init place d’emblée votre projet sous contrôle de version, et le .gitignore qu’il écrit répond à la question la plus importante du versionnage – qu’est-ce qu’on partage, et qu’est-ce qu’on ne partage pas ?

En première partie, vous versionnez ce projet seul(e) et le publiez sur GitLab. En seconde partie, vous travaillez à deux sur le même dépôt, vous provoquez délibérément des conflits, et vous les résolvez.

Information

Gardez les fiches de cours ouvertes à côté : Git pour les commandes, GitLab pour l’interface web et les clés SSH, Installer Git si l’outil n’est pas encore sur votre machine.

Les questions signalées par demandent une réponse écrite, à garder pour la mise en commun de fin de séance.

Contenu de l’activité

Versionner un projet uv

Cette partie se fait en autonomie. Elle vous fait parcourir seul(e) le cycle complet : créer, enregistrer, publier, puis vérifier que quelqu’un d’autre pourrait repartir de votre dépôt.

Vérifier son installation

Git s’installe sur la machine, indépendamment de GitLab, et réclame deux réglages avant le premier commit : votre nom et votre adresse électronique. C’est ce que Git inscrira dans chacune de vos versions.

À faire

Vérifiez que Git répond, que user.name et user.email sont renseignés, et que uv est disponible.

git --version
git config --list --show-origin
uv --version
git --version
git config --list --show-origin
uv --version
git --version
git config --list --show-origin
uv --version
git --version
git config --list --show-origin
uv --version

Si user.name ou user.email n’apparaissent pas dans la configuration, renseignez-les en suivant la fiche Installer Git, avec l’adresse électronique de l’école.

La commande git config --list --show-origin indique de quel fichier provient chaque réglage. Lequel contient votre nom, et pourquoi n’est-il pas dans le dossier du projet ?

Créer un projet et découvrir le dépôt qu’il contient

uv init ne fait pas que préparer un projet Python : il exécute aussi git init pour vous. Vous allez donc vous trouver, sans avoir tapé une seule commande Git, dans un dépôt déjà constitué.

À faire

Créez un dossier calculatrice dans votre arborescence de cours, initialisez-y un projet uv, puis demandez à Git l’état du dossier.

mkdir imt\info\env\session3\calculatrice
cd imt\info\env\session3\calculatrice
uv init --no-package
git status
New-Item -ItemType Directory -Path imt\info\env\session3\calculatrice -Force
cd imt\info\env\session3\calculatrice
uv init --no-package
git status
mkdir -p imt/info/env/session3/calculatrice
cd imt/info/env/session3/calculatrice
uv init --no-package
git status
mkdir -p imt/info/env/session3/calculatrice
cd imt/info/env/session3/calculatrice
uv init --no-package
git status
Information

Il peut arriver que uv init ne crée pas les fichiers nécessaires pour Git (selon la version de uv, par exemple). Vous pouvez forcer ce comportement en ajoutant l’option --vcs git à la commande uv init.

Ouvrez ensuite le dossier calculatrice dans VS Code et affichez le contenu du fichier .gitignore.

Git a répondu alors que vous n’avez tapé aucune commande Git : où est le dépôt, et qui l’a créé ? Combien de commits contient-il ? Que contient .gitignore, et qu’est-ce que cela vous dit sur ce que uv considère comme partageable ?

Cliquez ici pour afficher la réponse.

uv init a exécuté git init pour vous : un dossier caché .git est apparu à la racine du projet. C’est lui, et lui seul, qui fait de ce dossier un dépôt Git.

L’historique est vide : git status annonce No commits yet. Le dépôt existe, mais aucune version n’a encore été enregistrée. Créer un dépôt et enregistrer une version sont deux choses différentes.

Le .gitignore écrit par uv contient notamment .venv et __pycache__/. Autrement dit : l’environnement virtuel et les fichiers compilés ne seront jamais proposés au versionnage. Vous versionnerez pyproject.toml et uv.lock, c’est-à-dire la recette de l’environnement, et jamais l’environnement lui-même – qui se reconstruit d’une commande sur n’importe quelle machine.

Enregistrer une première version

Deux étapes séparent votre travail d’une version enregistrée : git add choisit ce qui entrera dans la version, git commit l’enregistre avec un message. Prenez le réflexe d’encadrer chaque commande par un git status, qui vous montre l’effet de ce que vous venez de faire.

À faire

Ajoutez tout le contenu du projet à l’index, enregistrez-le dans une version dont le message décrit ce qu’elle contient, et affichez l’historique.

git add .
git status
git commit -m "Initialise le projet uv de la calculatrice"
git log --oneline
git add .
git status
git commit -m "Initialise le projet uv de la calculatrice"
git log --oneline
git add .
git status
git commit -m "Initialise le projet uv de la calculatrice"
git log --oneline
git add .
git status
git commit -m "Initialise le projet uv de la calculatrice"
git log --oneline

Listez les fichiers que ce premier commit contient. Le dossier .venv existe-t-il sur votre disque ? Apparaît-il dans le commit ? Pourquoi ?

Ajouter une dépendance et observer ce que Git en dit

C’est ici que le choix d’un projet uv prend tout son sens : une seule commande va modifier trois choses sur votre disque, et Git ne va en voir que deux.

À faire

Ajoutez numpy comme dépendance du projet, observez ce que Git signale, puis enregistrez cette évolution dans une version dédiée.

uv add numpy
git status
git add pyproject.toml uv.lock
git commit -m "Ajoute numpy aux dependances du projet"
uv add numpy
git status
git add pyproject.toml uv.lock
git commit -m "Ajoute numpy aux dependances du projet"
uv add numpy
git status
git add pyproject.toml uv.lock
git commit -m "Ajoute numpy aux dépendances du projet"
uv add numpy
git status
git add pyproject.toml uv.lock
git commit -m "Ajoute numpy aux dépendances du projet"

uv add numpy a modifié ou créé trois choses sur votre disque. Lesquelles ? Combien d’entre elles apparaissent dans git status ? Quelle est, en une phrase, la règle générale que cela illustre ?

Cliquez ici pour afficher la réponse.

uv add numpy a : modifié pyproject.toml (la dépendance déclarée), créé ou mis à jour uv.lock (les versions exactes résolues), et rempli .venv (les fichiers installés).

Seules les deux premières apparaissent dans git status, puisque .venv est ignoré. La règle : on versionne la recette, jamais le plat cuisiné. pyproject.toml dit ce dont le projet a besoin, uv.lock dit exactement quelles versions le satisfont, et n’importe qui peut reconstruire .venv à l’identique avec uv sync.

C’est aussi ce qui garde le dépôt léger : un .venv avec numpy pèse plusieurs dizaines de mégaoctets, contre quelques kilooctets pour les deux fichiers de configuration.

Publier le projet sur GitLab

Votre dépôt est pour l’instant purement local, et il fonctionne très bien ainsi. Lui associer un dépôt distant ajoute deux choses : une sauvegarde ailleurs, et la possibilité de partager.

À faire

Créez sur GitLab un projet nommé calculatrice, sans fichier README, récupérez son URL de clonage, puis associez-le à votre dépôt local et envoyez votre travail.

Connectez-vous sur https://gitlab-df.imt-atlantique.fr pour créer le projet. N’ajoutez pas de README : votre dépôt local a déjà son histoire, et un commit créé par GitLab entrerait immédiatement en conflit avec elle.

git remote add origin <url du depot distant>
git remote -v
git push -u origin main
git remote add origin <url du depot distant>
git remote -v
git push -u origin main
git remote add origin <url du depot distant>
git remote -v
git push -u origin main
git remote add origin <url du depot distant>
git remote -v
git push -u origin main

Rechargez la page du projet sur GitLab : vos fichiers et vos trois commits doivent y être.

Sur GitLab, ouvrez la liste des fichiers du projet. Le dossier .venv y est-il ? Et uv.lock ? Comparez avec ce que contient votre dossier local, et expliquez l’écart.

Vérifier que le projet est reproductible

C’est le test qui compte : quelqu’un d’autre peut-il faire tourner votre code à partir du seul dépôt ? Jouons ce rôle en clonant votre propre projet ailleurs sur le disque.

À faire

Depuis un dossier essai placé ailleurs sur votre disque, clonez votre projet, constatez l’absence de .venv, puis reconstruisez l’environnement et exécutez le programme.

cd %USERPROFILE%\essai
git clone <url du depot distant>
cd calculatrice
uv sync
uv run python main.py
cd $HOME\essai
git clone <url du depot distant>
cd calculatrice
uv sync
uv run python main.py
cd ~/essai
git clone <url du depot distant>
cd calculatrice
uv sync
uv run python main.py
cd ~/essai
git clone <url du depot distant>
cd calculatrice
uv sync
uv run python main.py

Combien de commandes ont été nécessaires pour rendre le projet exécutable à partir de rien ? Quelle version de numpy a été installée, et pourquoi êtes-vous certain(e) que c’est exactement la même que dans votre dossier d’origine ?

Travailler à plusieurs

Constituez un binôme. Dans ce qui suit, l’une des deux personnes est désignée comme A, l’autre comme B. Vous allez partir du dépôt de A et y travailler à deux.

Partager un dépôt

Donner accès à quelqu’un se fait entièrement côté GitLab : Git, lui, ne connaît pas la notion de membre. Le rôle Developer suffit pour pousser du code.

À faire

A ajoute B au projet calculatrice avec le rôle Developer (menu Manage > Members). B clone ensuite le projet et reconstruit l’environnement.

git clone <url du depot de A>
cd calculatrice
uv sync
uv run python main.py
git clone <url du depot de A>
cd calculatrice
uv sync
uv run python main.py
git clone <url du depot de A>
cd calculatrice
uv sync
uv run python main.py
git clone <url du depot de A>
cd calculatrice
uv sync
uv run python main.py

B n’a reçu ni fichier par messagerie, ni consigne d’installation, et son environnement est identique à celui de A. Qu’est-ce qui a rendu cela possible ?

Mettre du code dans le projet

Le projet ne contient encore que le main.py d’exemple. Ajoutons-y de quoi travailler à deux.

À faire

A crée le fichier calculatrice.py donné ci-dessous, le versionne et l’envoie sur le dépôt distant. B le récupère avec git pull.

"""Une calculatrice minimale, en ligne de commande."""

def addition(a, b):
    return a + b

def soustraction(a, b):
    return a - b

def multiplication(a, b):
    return a * b

def division(a, b):
    if b == 0:
        return "Erreur : division par zéro."
    return a / b

def main():
    print("Bienvenue dans la calculatrice !")
    print("1. Addition")
    print("2. Soustraction")
    print("3. Multiplication")
    print("4. Division")

    choix = input("Votre choix (1/2/3/4) : ")
    if choix not in ["1", "2", "3", "4"]:
        print("Choix invalide.")
        return

    a = float(input("Premier nombre : "))
    b = float(input("Second nombre : "))
    operations = {"1": addition, "2": soustraction, "3": multiplication, "4": division}
    print(f"Résultat : {operations[choix](a, b)}")

if __name__ == "__main__":
    main()
git add calculatrice.py
git commit -m "Ajoute la calculatrice et ses quatre operations"
git push
git add calculatrice.py
git commit -m "Ajoute la calculatrice et ses quatre operations"
git push
git add calculatrice.py
git commit -m "Ajoute la calculatrice et ses quatre opérations"
git push
git add calculatrice.py
git commit -m "Ajoute la calculatrice et ses quatre opérations"
git push

Provoquer un conflit sur les dépendances

Voici un conflit très fréquent en projet, et propre aux projets uv : deux personnes ajoutent une bibliothèque en même temps. Le premier push passe, le second est refusé.

À faire

Sans vous concerter et sans faire de pull, A ajoute scipy et B ajoute matplotlib, puis chacun(e) versionne et pousse.

uv add scipy
git add pyproject.toml uv.lock
git commit -m "Ajoute une dependance"
git push
uv add scipy
git add pyproject.toml uv.lock
git commit -m "Ajoute une dependance"
git push
uv add scipy
git add pyproject.toml uv.lock
git commit -m "Ajoute une dépendance"
git push
uv add scipy
git add pyproject.toml uv.lock
git commit -m "Ajoute une dépendance"
git push

La personne dont le push est refusé fait un git pull : il y a alors un conflit dans pyproject.toml, et un autre dans uv.lock. Les deux ne se traitent pas du tout de la même façon.

À faire

Résolvez le conflit de pyproject.toml à la main dans VS Code, en gardant les deux dépendances. Ne touchez pas à uv.lock : régénérez-le.

git checkout --ours uv.lock
uv lock
git add pyproject.toml uv.lock
git commit -m "Fusionne les dependances scipy et matplotlib"
git push
git checkout --ours uv.lock
uv lock
git add pyproject.toml uv.lock
git commit -m "Fusionne les dependances scipy et matplotlib"
git push
git checkout --ours uv.lock
uv lock
git add pyproject.toml uv.lock
git commit -m "Fusionne les dépendances scipy et matplotlib"
git push
git checkout --ours uv.lock
uv lock
git add pyproject.toml uv.lock
git commit -m "Fusionne les dépendances scipy et matplotlib"
git push

L’autre personne termine par un git pull puis un uv sync, et vérifie que scipy et matplotlib sont disponibles.

Pourquoi résout-on pyproject.toml à la main, mais surtout pas uv.lock ? Que se passerait-il si vous choisissiez « garder ma version » sur uv.lock sans lancer uv lock ensuite ?

Cliquez ici pour afficher la réponse.

pyproject.toml est écrit pour les humains : quelques lignes lisibles qui déclarent une intention. Fusionner deux ajouts y est trivial, il suffit de garder les deux lignes.

uv.lock est généré : plusieurs centaines de lignes décrivant les versions exactes de toutes les dépendances, y compris indirectes, avec leurs sommes de contrôle. Le fusionner à la main n’a aucun sens et produirait un fichier incohérent. La bonne méthode est donc toujours : réparer l’intention dans pyproject.toml, puis demander à l’outil de recalculer le verrou avec uv lock.

Si vous gardiez votre version de uv.lock sans le régénérer, il resterait cohérent mais incomplet : il ne mentionnerait pas la dépendance ajoutée par l’autre personne. Un uv sync sur une machine neuve n’installerait pas cette bibliothèque, et le code de votre binôme échouerait sur un ModuleNotFoundError – alors que pyproject.toml, lui, semble correct. Ce genre d’incohérence entre les deux fichiers est particulièrement pénible à diagnostiquer.

Provoquer un conflit sur le code

Le conflit précédent portait sur des fichiers de configuration. Voyons maintenant celui que vous rencontrerez le plus souvent : deux personnes modifient les mêmes lignes du même fichier de code.

À faire

Après un git pull de chaque côté, répartissez-vous les deux tâches ci-dessous, qui touchent volontairement les mêmes lignes de calculatrice.py. Chacun(e) travaille, versionne et pousse ; le second push sera refusé.

  • Tâche 1 – Ajouter une opération modulo, qui renvoie le reste de la division du premier nombre par le second, et l’ajouter au menu et au dictionnaire operations.
  • Tâche 2 – Valider les entrées : refuser proprement une saisie qui n’est pas un nombre, en affichant un message clair plutôt qu’une trace d’erreur.

Faites ensuite un git pull, ouvrez le fichier dans VS Code et utilisez l’éditeur de fusion pour construire une version qui contient les deux contributions. Terminez par le cycle habituel add, commit, push, et vérifiez que le binôme récupère bien le résultat.

Comparez ce conflit avec celui de l’étape précédente : lequel Git aurait-il pu résoudre seul, et pourquoi ne l’a-t-il pas fait ? Quelle organisation, en amont, vous aurait évité celui-ci ?

Documenter le projet

GitLab affiche automatiquement le fichier README.md placé à la racine du dépôt, sur la page d’accueil du projet. C’est la première chose que verra quelqu’un qui découvre votre travail – correcteur(rice) inclus(e).

À faire

À deux, complétez le README.md créé par uv, versionnez-le et vérifiez son rendu sur la page d’accueil GitLab du projet.

Votre README.md doit contenir au minimum :

  1. ce que fait le projet, en deux phrases ;
  2. comment l’installer et le lancer – soit exactement les deux commandes que B a tapées après le clonage ;
  3. la liste des opérations disponibles ;
  4. les noms des deux contributeur(rice)s.

Pour la syntaxe, voyez la référence Markdown et les spécificités GitLab.

Important

Un projet non documenté est un projet inutilisable. Ce n’est pas une formule : sans README, personne ne sait par quelle commande lancer votre code, et votre travail devient invérifiable.

Votre README.md permettrait-il à un(e) camarade d’un autre binôme de faire tourner votre calculatrice sans vous poser de question ? Testez-le pour de vrai en échangeant vos URL avec un autre binôme.

Gérer le dépôt depuis VS Code

Tout ce que vous venez de faire en ligne de commande est aussi accessible depuis l’IDE. Maintenant que vous savez ce qui se passe dessous, l’interface graphique devient un confort et non une boîte noire.

Source control

À faire

Faites une modification mineure dans calculatrice.py, puis effectuez un cycle complet add / commit / push sans quitter VS Code, depuis le panneau Source Control.

Vous y retrouverez, dans l’ordre : la liste des fichiers modifiés, le bouton + qui ajoute à l’index, la zone de message de commit, et le bouton de synchronisation qui envoie vers le dépôt distant.

Pour aller plus loin dans l’intégration, installez l’extension GitLab pour VS Code et authentifiez-vous auprès de l’instance de l’école. Il vous faudra créer un jeton (token) depuis votre profil GitLab, avec la portée api. Activez ensuite l’affichage des auteurs ligne par ligne, via le réglage git.blame.editorDecoration.enabled :

Config GitLab Extension

En plaçant le curseur sur une ligne, VS Code indique le message de commit, l’auteur(e) et la date de sa dernière modification.

Sur une ligne écrite par votre binôme, retrouvez le message de commit associé. Ce message vous explique-t-il pourquoi la ligne est là ? Reformulez-le comme vous auriez aimé le lire.

Pour aller plus loin

Travailler sur une branche

Jusqu’ici, vous avez tous les deux travaillé sur main, d’où les conflits. La pratique courante en équipe est de créer une branche par tâche, puis de la fusionner quand elle est terminée.

git switch -c ajout-modulo
git push -u origin ajout-modulo
git switch -c ajout-modulo
git push -u origin ajout-modulo
git switch -c ajout-modulo
git push -u origin ajout-modulo
git switch -c ajout-modulo
git push -u origin ajout-modulo

Sur GitLab, cette branche peut alors faire l’objet d’une merge request : votre binôme relit vos modifications et les commente avant qu’elles n’entrent dans main. C’est le mode de travail que vous adopterez pour le projet du semestre.

Retrouver un état antérieur

Tout l’intérêt de versionner est de pouvoir revenir en arrière. Explorez l’historique, puis affichez l’état d’un fichier à un commit donné.

git log --oneline --graph --all
git show <debut du hash>:calculatrice.py
git log --oneline --graph --all
git show <debut du hash>:calculatrice.py
git log --oneline --graph --all
git show <début du hash>:calculatrice.py
git log --oneline --graph --all
git show <début du hash>:calculatrice.py

Et si on avait versionné .venv ?

Pour bien mesurer l’intérêt du .gitignore, essayez : retirez .venv du .gitignore, faites git add ., et regardez le nombre de fichiers que git status annonce, puis la taille du dossier. Ne validez pas ce commit : annulez avec git restore --staged . et remettez la ligne dans .gitignore.

Information

Y a-t-il quelque chose que vous auriez aimé voir ici ? Faites-le nous savoir sur le serveur Discord ! Peut-être pouvons-nous l’ajouter rapidement. Sinon, cela nous aidera à améliorer le cours pour l’année prochaine !