← Retour à l'accueil

Cours 02

Flows de développement

Après les bases Git et GitHub, cette séance montre comment organiser le travail collectif avec Docker, GitHub Flow et Git Flow.

QCM d'ouverture

La séance commence par 20 minutes de QCM sur papier. Les questions portent sur le cours-01 : versioning, Git en local, dépôts distants, branches, conflits, releases et Conventional Commits.

Posez le stylo quand le temps est écoulé.

Tag et release GitHub

Tag

Un tag est un pointeur nommé vers un commit précis. Il sert à marquer une version stable (v1.0.0, v2.3.1…) et ne bouge pas comme une branche : main continue d'avancer, mais le tag reste attaché au commit choisi.

Dans GitHub, les tags apparaissent dans la page des releases et dans l'historique du dépôt. Ils donnent un repère partagé : « cette version du code correspond à ce jalon ». Sans tag, il devient difficile de retrouver exactement le code livré à une date donnée.

Release GitHub

Une release GitHub est construite à partir d'un tag. Elle donne une forme lisible au jalon technique : un titre, des notes de version, un changelog et parfois des artefacts téléchargeables.

Le tag répond à la question « quel commit ? ». La release répond à la question « qu'est-ce qui a changé et que peut-on utiliser ? ». C'est le lien entre l'historique Git et une livraison compréhensible par une équipe, un client ou un utilisateur.

Exemple : github.com/GVI2025/SimpleWMS/releases/tag/v1.0.0

Travaux pratiques

  1. Ouvrir sur GitHub le dépôt personnel de votre choix.
  2. Aller dans Releases, puis choisir Draft a new release.
  3. Dans le formulaire de release, renseigner un nouveau tag, par exemple v1.0.0, et le baser sur la branche main. GitHub créera ce tag au moment de la publication de la release.
  4. Rédiger un titre court et des notes de version qui expliquent ce que représente cette version.
  5. Publier la release, puis vérifier que le tag et la release sont visibles dans le dépôt.

Docker : introduction

Avant de pratiquer les workflows Git sur de vraies applications, il faut un environnement de développement commun. Docker résout le problème classique : « ça marche sur ma machine ».

En encapsulant l'application et son environnement dans une unité portable, Docker garantit que le comportement sera identique sur toutes les machines de l'équipe — et sur les serveurs.

Trois concepts fondamentaux

Image : instantané immuable d'un système de fichiers avec un environnement d'exécution. Elle est construite une fois, puis distribuée via un registry. Elle ne tourne pas toute seule.

Conteneur : instance en cours d'exécution d'une image. Plusieurs conteneurs peuvent tourner à partir de la même image, isolés les uns des autres.

Registry : entrepôt d'images. Docker Hub est le registry public de référence — docker pull node:24-alpine télécharge depuis Docker Hub.

Cycle de vie

Chargement du diagramme…

Commandes essentielles

docker pull node:24-alpine

Télécharger une image depuis Docker Hub.

docker image ls

Lister les images disponibles localement.

docker build -t mon-image .

Construire une image depuis le Dockerfile du répertoire courant et lui donner le nom mon-image.

docker run -p 3000:3000 mon-image

Créer et démarrer un conteneur. L'option -p 3000:3000 expose le port 3000 du conteneur sur le port 3000 de la machine hôte.

docker ps

Lister les conteneurs actifs. Ajouter -a pour voir aussi les conteneurs arrêtés.

docker stop <id>
docker rm <id>

Arrêter puis supprimer un conteneur. L'arrêt doit précéder la suppression.

docker rmi mon-image

Supprimer une image locale. Elle ne doit plus être utilisée par aucun conteneur.

docker push mon-compte/mon-image

Publier une image vers un registry (Docker Hub ou autre).

Le Dockerfile

Un Dockerfile décrit comment construire une image. Chaque instruction crée une couche supplémentaire dans l'image finale.

FROM node:24-alpine

WORKDIR /app

COPY . .

RUN npm install

EXPOSE 3000

CMD ["node", "index.js"]

FROM : image parente à utiliser comme base. Point de départ obligatoire de tout Dockerfile.

WORKDIR : répertoire de travail dans le conteneur. Toutes les instructions suivantes s'y exécutent.

COPY : copier des fichiers depuis la machine hôte dans l'image.

RUN : exécuter une commande pendant la construction de l'image (installation de dépendances, compilation…).

EXPOSE : déclarer le port sur lequel l'application écoute. C'est surtout de la documentation — le port doit quand même être mappé avec -p au lancement du conteneur.

CMD : commande exécutée au démarrage du conteneur. Un seul CMD par Dockerfile.

Quelle est la différence entre une image et un conteneur ?

Une image est un modèle immuable — comme une classe en programmation orientée objet. Elle ne tourne pas.

Un conteneur est une instance en cours d'exécution d'une image. Plusieurs conteneurs indépendants peuvent être lancés à partir de la même image.

Analogie : image = plan d'appartement, conteneur = appartement construit et habité.

TP 1 — Partie 1 : Docker pratique (25 min)

Cette partie est indépendante du reste du projet. Elle se fait entièrement dans le sous-dossier docker-practice/. Le but est de manipuler les commandes Docker fondamentales sur un cas très simple : un serveur HTTP minimaliste en Node.js pur. Le dépôt doit être ouvert dans un DevContainer, toutes les commandes doivent être exécutées à l'intérieur de ce DevContainer, et le travail doit être réalisé sur une branche dédiée, par exemple feature/docker-practice.

Dépôt support (à forker sur GitHub avant de cloner) : github.com/GVI2026/tp-github-flow

  1. Forker le dépôt sur GitHub, puis le cloner localement.
  2. Ouvrir le dépôt dans son DevContainer, puis créer une branche dédiée :
    git checkout -b feature/docker-practice
  3. Se placer dans le sous-dossier docker-practice/.
  4. Ouvrir index.js — lire ce que fait ce serveur HTTP avant de continuer.
  5. Ouvrir Dockerfile — compléter chaque section guidée par les commentaires (FROM, WORKDIR, COPY, EXPOSE, CMD).
  6. Construire l'image :
    docker build -t hello-docker .
  7. Vérifier que l'image apparaît localement :
    docker image ls
  8. Lancer un conteneur :
    docker run -p 3001:3001 hello-docker
  9. Dans un autre terminal, vérifier que l'application répond :
    curl http://localhost:3001
    Résultat attendu : Hello depuis Docker !
  10. Trouver l'ID du conteneur actif, puis l'arrêter :
    docker ps
    docker stop <id>
  11. Supprimer le conteneur :
    docker rm <id>
  12. Supprimer l'image locale et vérifier le nettoyage :
    docker rmi hello-docker
    docker image ls
    L'image hello-docker ne doit plus apparaître.

Exercices optionnels rapides

  • Lire les logs du conteneur avec docker logs <id>.
  • Entrer dans un conteneur actif avec docker exec -it <id> sh.
  • Passer une variable d'environnement au lancement avec docker run -e MESSAGE="Bonjour" ....
  • Observer le principe d'un Dockerfile multi-stage très simple dans le README du dépôt support.
Concepts à vérifier à la fin de la manipulation

Quelle est la différence entre une image et un conteneur ?

Que fait la ligne FROM dans un Dockerfile ?

À quoi sert -p 3000:3000 dans docker run ?

Quel est l'ordre correct : build → run → stop → rm → rmi ?

GitHub Flow

GitHub Flow est un workflow simple et très courant. Il repose sur une seule branche stable (main) et des branches de fonctionnalité à durée courte. Tout changement passe par une Pull Request avant d'intégrer main.

Il est facile à introduire quand une équipe découvre les Pull Requests et les protections de branche, mais il demande tout de même une intégration régulière, des branches courtes et une branche main toujours stable.

Le flux en six étapes

  1. Créer une branche descriptive depuis main :
    git checkout -b feature/ma-fonctionnalite
  2. Faire des commits fréquents et lisibles (Conventional Commits) :
    git commit -m "feat: ajout du filtrage par done"
  3. Pousser la branche sur GitHub :
    git push origin feature/ma-fonctionnalite
  4. Ouvrir une Pull Request, traiter les retours de relecture et mettre à jour la branche si besoin.
  5. Fusionner dans main et supprimer la branche.
  6. Préparer la release sur main avec un commit dédié qui met à jour les versions du projet (package.json, pom.xml…) et le changelog :
    git add <fichiers-version> CHANGELOG.md
    git commit -m "chore(release): bump project to version to 1.0.0"
    Depuis GitHub, créer ensuite une release manuelle en renseignant un tag v1.0.0 basé sur ce commit de release, avec un titre et des notes de version. Cette étape peut ensuite être automatisée dans une pipeline CI/CD.

Chargement du gitGraph…

Options de merge sur GitHub

Au moment de fusionner une PR, trois options sont disponibles :

Merge commit : conserve tous les commits de la branche et ajoute un commit de merge — historique complet, mais lecture parfois compliquée, peu lisible et verbeuse.

Squash and merge : regroupe tous les commits en un seul avant la fusion — historique de main propre et linéaire.

Rebase and merge : rejoue les commits en linéaire sur main sans commit de merge.

Avantages et limites

✅ Simple à apprendre et à appliquer dès le premier jour.

✅ Adapté aux petites équipes et aux projets web ou SaaS.

✅ Idéal pour un déploiement fréquent et l'intégration continue.

⚠️ Moins adapté aux livraisons en batch avec une QA manuelle longue.

⚠️ Pas de séparation explicite entre code « en intégration » et code « en production ».

Pourquoi la branche main doit-elle rester en dehors du développement direct ?

main représente le code livrable à tout moment. Développer directement dessus introduit du code instable ou partiel dans l'état de référence de l'équipe.

Une branche courte permet d'isoler les modifications jusqu'à ce qu'elles soient validées — par les tests automatiques et la revue de code — avant d'intégrer main.

Rappel Conventional Commits

Avant la Pull Request, chaque commit doit résumer l'intention du changement avec un type court et stable.

feat: filter tasks by done status
fix: reject empty task title
docs: clarify pull request instructions

Le type feat signale une fonctionnalité, fix un correctif, docs une modification documentaire. Cette convention facilite la relecture et prépare les changelogs futurs.

TP 1 — Partie 2 : GitHub Flow (25 min)

Cette partie se fait sur le même fork que la Partie 1 — cette fois à la racine du projet, pas dans docker-practice/.

Objectif : implémenter la route GET /tasks?done=true et GET /tasks?done=false. Pour cet exercice, le paramètre done est obligatoire et permet de filtrer les tâches terminées ou non terminées, en suivant le GitHub Flow complet.

  1. S'assurer d'être sur main et à jour :
    git checkout main
    git pull
  2. Créer une branche de travail :
    git checkout -b feature/add-task-filter
  3. Implémenter le filtrage booléen sur done dans TasksService et TasksController. Le paramètre done attendu est documenté dans le README du dépôt.
  4. Vérifier que tous les tests passent :
    npm test
  5. Committer proprement :
    git add .
    git commit -m "feat: filter tasks by done status"
  6. Pousser la branche :
    git push origin feature/add-task-filter
  7. Ouvrir une Pull Request vers main sur GitHub. Rédiger un titre explicite et une courte description.
  8. Traiter une remarque simulée ou réelle — adapter le code ou le test en conséquence.
  9. Faire approuver la Pull Request et la fusionner dans main.
  10. Vérifier que main reste dans un état fonctionnel après la fusion.
  11. Préparer le passage en 1.0.0 sur main : mettre à jour package.json et package-lock.json, créer ou mettre à jour CHANGELOG.md, puis committer :
    git add package.json package-lock.json CHANGELOG.md
    git commit -m "chore(release): bump project to version to 1.0.0"
    Depuis GitHub, créer ensuite une release manuelle en renseignant un tag v1.0.0 basé sur ce commit de release.
Extension optionnelle — pagination

Implémenter la pagination sur GET /tasks via les query params ?page= et ?limit=.

Exemple : GET /tasks?page=1&limit=10

Même démarche : nouvelle branche feature/add-pagination, Pull Request vers main, merge.

Git Flow

Git Flow est un modèle historique et encore utile dans certains contextes, mais ce n'est pas le flow supérieur : son cadre apporte aussi un coût en complexité. Popularisé par Vincent Driessen en 2010, il convient aux équipes qui livrent par versions planifiées plutôt qu'en continu. Il introduit une séparation explicite entre le code en production et le code en cours d'intégration.

Branches principales

main : code en production. Chaque commit y est tagué et correspond à une release. Intouchable sauf pour les hotfix.

develop : code stabilisé, non encore livré. Reçoit les merges des branches feature/* et sert de base aux branches release/*.

Branches secondaires

feature/* : issue de develop. Contient le développement d'une fonctionnalité. Fusionnée dans develop une fois terminée, puis supprimée.

release/* : issue de develop. Permet uniquement des corrections mineures avant la livraison. Fusionnée dans main et dans develop à la fin — canoniquement depuis la branche de release elle-même, afin de propager les commits de stabilisation sans passer par main.

hotfix/* : issue de main. Correction urgente d'un bug en production. Fusionnée dans main et dans develop.

Chargement du gitGraph…

Récapitulatif des règles

feature/* : part de develop, retourne dans develop.

release/* : part de develop, retourne dans main + develop.

hotfix/* : part de main, retourne dans main + develop.

Dans ce flow, les branches sont réconciliées avec l'option Merge commit afin de conserver explicitement les points de jonction entre feature/*, release/*, hotfix/* et les branches permanentes.

Avantages et limites

✅ Idéal pour les équipes avec des livraisons planifiées.

✅ Séparation claire entre code de production et code en intégration.

✅ Gestion explicite des correctifs urgents avec hotfix/*.

⚠️ Trop lourd pour un déploiement continu — trop de branches en parallèle.

⚠️ Le retour de release/* et hotfix/* dans develop est souvent oublié, ce qui crée des divergences.

⚠️ Moins adapté aux petites équipes qui livrent très fréquemment.

Depuis quelle branche une branche feature/* doit-elle être créée dans Git Flow ?

Depuis develop. Une feature ne part pas de main, car main représente le code livré.

main représente uniquement ce qui est en production. Le développement courant vit sur develop.

Quelle réconciliation est nécessaire lorsqu'une branche release/* est finalisée ?

La fusionner dans main avec un tag de version, pour marquer la livraison en production.

La fusionner aussi dans develop — canoniquement depuis la branche de release elle-même (git merge release/x.x.x) — pour que le développement en cours hérite des éventuelles corrections faites pendant la stabilisation.

Oublier le retour vers develop est l'erreur la plus fréquente dans Git Flow.

Dans un flux avec Pull Request sur GitHub (comme dans le TP), la branche de release est généralement supprimée après le merge dans main. Dans ce cas, git checkout develop && git merge main est l'équivalent pratique : au moment du merge, main et la branche release/* pointent exactement vers le même commit.

TP 2 : Git Flow (35 min)

Objectif : pratiquer le cycle complet de Git Flow — feature, intégration dans develop, préparation d'une release et livraison dans main.

Dépôt support (à forker sur GitHub avant de cloner) : github.com/GVI2026/tp-git-flow

Étape 0 — Mise en place

  1. Forker le dépôt sur GitHub, puis le cloner localement.
  2. Vérifier que les deux branches permanentes existent :
    git branch -a
    Résultat attendu : main et origin/develop.
  3. Se placer sur develop :
    git checkout develop

Étape 1 — Créer et intégrer une feature

  1. Créer une branche depuis develop :
    git checkout -b feature/add-task-stats
  2. Implémenter GET /tasks/stats qui retourne { total, done, pending }.
  3. Décommenter le bloc TODO dans tasks.service.spec.ts et adapter le test.
  4. Vérifier que tous les tests passent :
    npm test
  5. Committer :
    git add .
    git commit -m "feat: add task stats endpoint"
  6. Fusionner dans develop et supprimer la branche :
    git checkout develop
    git merge feature/add-task-stats
    git branch -d feature/add-task-stats

Étape 2 — Préparer la release

  1. Créer une branche de release depuis develop :
    git checkout -b release/1.1.0
  2. Mettre à jour le fichier VERSION :
    1.1.0
  3. Committer cette préparation :
    git commit -am "chore: prepare release 1.1.0"

Étape 3 — Finaliser la release

  1. Pousser la branche de release :
    git push origin release/1.1.0
  2. Ouvrir une Pull Request release/1.1.0 → main sur GitHub et la faire merger.
  3. Après merge, créer un tag sur main :
    git checkout main
    git pull
    git tag v1.1.0
    git push origin v1.1.0
  4. Répercuter la release dans develop — étape critique à ne pas oublier :
    git checkout develop
    git merge main
    git push origin develop
Extension optionnelle — exercice hotfix

Simuler un correctif urgent en production. La validation du champ title est incomplète : un POST /tasks avec { "title": "" } accepte une chaîne vide.

  1. Créer une branche depuis main :
    git checkout main
    git checkout -b hotfix/fix-title-validation
  2. Corriger dans CreateTaskDto et écrire un test vérifiant que le titre vide est rejeté.
  3. Merger dans main avec un tag v1.1.1 :
    git commit -m "fix: reject empty task title"
    git checkout main
    git merge hotfix/fix-title-validation
    git tag v1.1.1
    git push origin main v1.1.1
  4. Répercuter dans develop :
    git checkout develop
    git merge main
    git push origin develop

Pour aller plus loin

nvie.com — A successful Git branching model : l'article original de Vincent Driessen qui a popularisé Git Flow.

docs.github.com — GitHub Flow : la documentation officielle de GitHub Flow.

Flow Branches permanentes Contexte adapté Avantage principal Piège principal
GitHub Flow 1 : main Livraison fréquente, PR courtes, CI active. Simplicité et boucle de feedback rapide. Peu de séparation entre intégration et production.
Git Flow 2 : main et develop Versions planifiées, stabilisation dédiée, hotfix explicites. Cadre clair pour releases et corrections urgentes. Complexité et retours vers develop oubliés.

La séance suivante comparera GitHub Flow et Git Flow avec d'autres workflows pour identifier quel flow convient à quel contexte d'équipe et de livraison.