← Retour à l'accueil

Cours 02

Flows de développement

Cette séance structure le travail en équipe autour de deux workflows majeurs : GitHub Flow, simple et continu, et Git Flow, plus cadré pour les livraisons planifiées. Docker y est introduit de façon pragmatique pour homogénéiser les environnements de développement.

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é.

Commit, tag et release — et après ?

Avant d'aborder les workflows, revenons sur trois notions du cours-01 qui en sont le socle.

Commit

Un commit est l'unité de modification du code, identifiée par un hash SHA. Il enregistre un instantané précis du projet à un moment donné. L'historique d'un dépôt est un enchaînement de commits.

Tag

Un tag est un pointeur nommé vers un commit précis. Il sert à marquer une version (v1.0.0, v2.3.1…) et ne bouge plus une fois posé, contrairement à une branche.

Release

Une release est une notion métier qui s'appuie sur un tag et représente une version prête à être diffusée. Sur GitHub, elle peut contenir des binaires, des notes de version et un changelog.

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

Vers des releases automatisées

La création d'une release peut être automatisée. Des outils comme release-please (Google) analysent les messages de Conventional Commits, génèrent un changelog et créent une Pull Request dédiée. Au merge de cette PR, la release est publiée automatiquement sur GitHub.

Ce mécanisme illustre pourquoi les conventions de commit vues en cours-01 ont une valeur concrète : elles alimentent des automatisations réelles.

Quelle est la différence entre un tag Git et une release GitHub ?

Un tag est un pointeur Git sur un commit précis. Il vit dans le dépôt Git.

Une release est une notion GitHub construite sur un tag. Elle permet d'y associer des binaires téléchargeables, des notes de version rédigées et un changelog lisible.

Toute release repose sur un tag. Mais un tag n'implique pas forcément une release.

Ces trois notions posent le cadre. Mais une question se pose rapidement dans une équipe : dans quel ordre et sur quelle branche doit-on travailler ?

Sans règles communes, les équipes poussent directement sur main, les conflits s'accumulent, les livraisons deviennent imprévisibles et les correctifs urgents sont impossibles à isoler. C'est ce que viennent résoudre les workflows Git.

Docker : introduction pragmatique

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. On la construit une fois, on la distribue 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. On peut lancer plusieurs conteneurs indépendants à partir de la même image.

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

TP 1 — Partie 1 : Docker pratique

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.

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. Se placer dans le sous-dossier docker-practice/.
  3. Ouvrir index.js — lire ce que fait ce serveur HTTP avant de continuer.
  4. Ouvrir Dockerfile — compléter chaque section guidée par les commentaires (FROM, WORKDIR, COPY, EXPOSE, CMD).
  5. Construire l'image :
    docker build -t hello-docker .
  6. Vérifier que l'image apparaît localement :
    docker image ls
  7. Lancer un conteneur :
    docker run -p 3001:3001 hello-docker
  8. Dans un autre terminal, vérifier que l'application répond :
    curl http://localhost:3001
    Vous devez obtenir : Hello depuis Docker !
  9. Trouver l'ID du conteneur actif, puis l'arrêter :
    docker ps
    docker stop <id>
  10. Supprimer le conteneur :
    docker rm <id>
  11. 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.
Ce que vous devez être capable d'expliquer à la fin

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

Que fait la ligne FROM dans un Dockerfile ?

Pourquoi utilise-t-on -p 3000:3000 dans docker run ?

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

GitHub Flow

GitHub Flow est le workflow le plus simple. 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.

Le flux en cinq é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 statut"
  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.

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 parfois verbeux.

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 ne développe-t-on pas directement sur main dans GitHub Flow ?

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.

TP 1 — Partie 2 : GitHub Flow

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 qui retourne uniquement les tâches 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 par statut 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.
Pour les plus rapides — extension 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 workflow plus structuré, 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.

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.

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 crée-t-on une branche feature/* dans Git Flow ?

Depuis develop. On ne part jamais de main pour une feature.

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

Que doit-on faire quand 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 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.

TP 2 : Git Flow

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
    Vous devez voir 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
Pour les plus rapides — 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.

github.com/googleapis/release-please : outil d'automatisation des releases à partir des Conventional Commits.

La séance suivante comparera GitHub Flow et Git Flow avec d'autres workflows (GitLab Flow, Trunk-Based Development) pour identifier quel flow convient à quel contexte d'équipe et de livraison.