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.
Travaux pratiques
- Ouvrir sur GitHub le dépôt personnel de votre choix.
- Aller dans Releases, puis choisir Draft a new release.
-
Dans le formulaire de release, renseigner un nouveau tag, par
exemple
v1.0.0, et le baser sur la branchemain. GitHub créera ce tag au moment de la publication de la release. - Rédiger un titre court et des notes de version qui expliquent ce que représente cette version.
- 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
- Forker le dépôt sur GitHub, puis le cloner localement.
-
Ouvrir le dépôt dans son DevContainer, puis créer une branche
dédiée :
git checkout -b feature/docker-practice -
Se placer dans le sous-dossier
docker-practice/. -
Ouvrir
index.js— lire ce que fait ce serveur HTTP avant de continuer. -
Ouvrir
Dockerfile— compléter chaque section guidée par les commentaires (FROM,WORKDIR,COPY,EXPOSE,CMD). -
Construire l'image :
docker build -t hello-docker . -
Vérifier que l'image apparaît localement :
docker image ls -
Lancer un conteneur :
docker run -p 3001:3001 hello-docker -
Dans un autre terminal, vérifier que l'application répond :
Résultat attendu :curl http://localhost:3001Hello depuis Docker ! -
Trouver l'ID du conteneur actif, puis l'arrêter :
docker ps docker stop <id> -
Supprimer le conteneur :
docker rm <id> -
Supprimer l'image locale et vérifier le nettoyage :
L'imagedocker rmi hello-docker docker image lshello-dockerne 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
-
Créer une branche descriptive depuis
main:git checkout -b feature/ma-fonctionnalite -
Faire des commits fréquents et lisibles (Conventional
Commits) :
git commit -m "feat: ajout du filtrage par done" -
Pousser la branche sur GitHub :
git push origin feature/ma-fonctionnalite - Ouvrir une Pull Request, traiter les retours de relecture et mettre à jour la branche si besoin.
-
Fusionner dans
mainet supprimer la branche. -
Préparer la release sur
mainavec un commit dédié qui met à jour les versions du projet (package.json,pom.xml…) et le changelog :
Depuis GitHub, créer ensuite une release manuelle en renseignant un taggit add <fichiers-version> CHANGELOG.md git commit -m "chore(release): bump project to version to 1.0.0"v1.0.0basé 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.
-
S'assurer d'être sur
mainet à jour :git checkout main git pull -
Créer une branche de travail :
git checkout -b feature/add-task-filter -
Implémenter le filtrage booléen sur
donedansTasksServiceetTasksController. Le paramètredoneattendu est documenté dans le README du dépôt. -
Vérifier que tous les tests passent :
npm test -
Committer proprement :
git add . git commit -m "feat: filter tasks by done status" -
Pousser la branche :
git push origin feature/add-task-filter -
Ouvrir une Pull Request vers
mainsur GitHub. Rédiger un titre explicite et une courte description. - Traiter une remarque simulée ou réelle — adapter le code ou le test en conséquence.
-
Faire approuver la Pull Request et la fusionner dans
main. -
Vérifier que
mainreste dans un état fonctionnel après la fusion. -
Préparer le passage en
1.0.0surmain: mettre à jourpackage.jsonetpackage-lock.json, créer ou mettre à jourCHANGELOG.md, puis committer :
Depuis GitHub, créer ensuite une release manuelle en renseignant un taggit add package.json package-lock.json CHANGELOG.md git commit -m "chore(release): bump project to version to 1.0.0"v1.0.0basé 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
- Forker le dépôt sur GitHub, puis le cloner localement.
-
Vérifier que les deux branches permanentes existent :
Résultat attendu :git branch -amainetorigin/develop. -
Se placer sur
develop:git checkout develop
Étape 1 — Créer et intégrer une feature
-
Créer une branche depuis
develop:git checkout -b feature/add-task-stats -
Implémenter
GET /tasks/statsqui retourne{ total, done, pending }. -
Décommenter le bloc TODO dans
tasks.service.spec.tset adapter le test. -
Vérifier que tous les tests passent :
npm test -
Committer :
git add . git commit -m "feat: add task stats endpoint" -
Fusionner dans
developet 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
-
Créer une branche de release depuis
develop:git checkout -b release/1.1.0 -
Mettre à jour le fichier
VERSION:1.1.0 -
Committer cette préparation :
git commit -am "chore: prepare release 1.1.0"
Étape 3 — Finaliser la release
-
Pousser la branche de release :
git push origin release/1.1.0 -
Ouvrir une Pull Request
release/1.1.0 → mainsur GitHub et la faire merger. -
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 -
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.
-
Créer une branche depuis
main:git checkout main git checkout -b hotfix/fix-title-validation -
Corriger dans
CreateTaskDtoet écrire un test vérifiant que le titre vide est rejeté. -
Merger dans
mainavec un tagv1.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 -
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.