2026-10-31
Un workflow est un jeu de règles qui décrit comment les modifications circulent dans une équipe : quelles branches existent, combien de temps, qui fusionne quoi, sous quel contrôle. Git n’impose aucun de ces règlements — il les permet tous. Le choix d’un workflow est donc une décision de coordination, pas une contrainte technique : un modèle se juge au problème d’échelle qu’il résout et au coût qu’il impose en échange.
Quatre objectifs structurent la leçon. Ils sont formulés comme des critères vérifiables, en miroir du barème de l’exercice final : ce qui est exigé ici prépare ce qui y sera évalué.
Important
Un workflow ne produit ses effets qu’à trois conditions : formalisé (dans le README du projet), automatisé (par la CI et la protection des branches), appliqué sans exception — y compris par ses auteurs.
Chaque terme reçoit une définition bornée à l’usage de la leçon ; quand Les bases de Git a déjà posé le concept, la définition est renvoyée, pas réenseignée.
Définition — workflow (de collaboration)
Ensemble de conventions régissant la création, la fusion, la relecture et la publication des branches dans un dépôt partagé. Le modèle est indépendant de l’outil de gestion de versions ; Git se contente de le rendre exécutable.
Définition — pull request (PR) et merge request (MR)
Demande de fusion d’une branche dans une autre, déposée sur une forge et assortie d’une relecture, d’une discussion et d’une pipeline. Le mécanisme est unique, le nom dépend de l’écosystème : pull request sur GitHub, merge request sur GitLab. Les deux désignations s’abrègent PR et MR dans la suite.
Définition — intégration continue (CI)
Exécution automatique des vérifications de qualité (compilation, tests, lint) déclenchée par un événement de la forge — poussée ou pull request. Son résultat conditionne l’autorisation de fusionner.
Définition — livraison continue (CD)
Déploiement automatisé d’un état validé par la CI, selon une règle fixée à l’avance (chaque fusion sur la branche principale, chaque tag). Elle se distingue du déploiement continu en ce qu’une décision humaine peut subsister sur le chemin de la production.
Définition — protection de branche
Ensemble de règles côté serveur appliquées à une branche : push direct interdit, relecture et pipeline exigées avant fusion. Configurée sur la forge, pas dans Git — d’où son efficacité, qui vaut pour tous les contributeurs sans exception.
Définition — Conventional Commits
Convention de format des messages de validation (feat:, fix:, docs:…). Posée dans Les bases de Git (section « Convention — Conventional Commits ») ; elle sert ici de critère de la checklist de revue.
Définition — rebase et merge
Deux manières d’intégrer une branche dans une autre, analysées en section « Rebase et fusion : ce que fait chaque commande ». À retenir d’emblée : la fusion ajoute un commit à l’histoire, le rebassage rejoue les commits ailleurs sous des empreintes neuves.
Les six modèles suivent une grille commune : le problème d’échelle traité, le mouvement des réfs induit (modèle posé dans Les bases de Git, section « Branches et fusions »), le patron de commandes, les conditions d’adéquation énoncées en critères. Les blocs de commandes décrivent des patrons — ils citent des dépôts distants propres à chaque projet et ne s’exécutent pas ici ; les deux simulations de la leçon, seules exécutées, travaillent dans des dépôts locaux.
Problème traité. Une équipe très petite veut un point de convergence unique : tout le monde travaille sur la même branche, à la queue leu leu.
Mouvement des réfs. Une seule réf locale, main, qui glisse sur la réf distante du même nom ; aucune seconde branche à fusionner.
Adéquation. Le modèle fonctionne à condition que la confiance soit totale — disons deux personnes au plus — car rien n’impose de relecture : la CI, si elle existe, s’exécute après la poussée, plus avant. Coût : les conflits se règlent au moment du pull, donc après le travail et dans la précipitation.
Problème traité. Plusieurs sujets avancent en parallèle sans s’attendre mutuellement ; chacun doit pouvoir être intégré indépendamment des autres.
Mouvement des réfs. Une réf feature/<sujet> bifurque depuis main ; la ramène soit un commit de fusion à deux parents, soit, en cas de squash (réduction de toute la branche à un commit unique à l’intégration — analysé §5), un commit neuf posé sur main.
Adéquation. Compromis généraliste dès qu’une relecture est exigée : chaque sujet est isolé, la CI s’exécute sur la pull request. Coût : une branche qui traîne diverge et doit être resynchronisée — c’est le rôle du rebassage analysé plus bas.
Problème traité. Un produit livré par versions numérotées doit préparer une release sans interrompre le développement de la suivante, et pouvoir corriger une version ancienne déjà en production.
Mouvement des réfs. Deux réfs permanentes : main (l’état publié) et develop (l’intégration). Les feature/* naissent de develop et y reviennent ; les release/* naissent de develop et retournent aux deux ; les hotfix/* naissent de main et retournent aux deux. Tous ces mouvements sont des fusions ordinaires : le modèle n’ajoute aucun mécanisme, seulement une discipline de nommage et de chronologie.
Vincent Driessen a formalisé cette organisation en 2010 sous le nom de Gitflow ; le modèle précède l’ère du déploiement continu, ce qui explique sa lourdeur dans les projets web.
Adéquation. Pertinent pour les logiciels livrés en paquets (application mobile, installation sur site), où une version a un début et une fin. Coût : chaque intégration tient une double comptabilité (develop, puis main) ; le modèle devient une entrave pour un produit déployé quotidiennement.
Problème traité. Publier souvent sans perdre la relecture : la chorégraphie de releases de Gitflow devient une gêne dès que la production est à un clic.
Mouvement des réfs. Une seule réf permanente, main, continuellement déployable ; les branches sont courtes, naissent de main et y meurent par fusion.
Adéquation. Équipes dotées d’une CI rapide et fiable, pull requests de taille réduite. C’est le modèle que met en œuvre l’exercice final de cette leçon : branche, pull request, relecture, fusion.
Problème traité. Accepter les contributions d’auteurs sans droit d’écriture sur le dépôt central : logiciels libres, projets de cours, échanges internes entre services.
Mouvement des réfs. Deux dépôts distants : l’amont (upstream) et le fork du contributeur. La branche locale glisse sur upstream/main par fetch puis rebase ; la branche de contribution est poussée vers le fork, d’où part la pull request vers l’amont.
Adéquation. Modèle de référence dès que les contributeurs n’ont aucun droit : le contrôle d’accès se joue à la pull request, que le mainteneur relit chez lui. Coût : la double synchronisation (origin et upstream), exécutée en simulation dans la section qui suit.
Problème traité. Supprimer la divergence à la source : moins il y a de branches, moins il y a de fusions, et plus tôt les erreurs d’intégration apparaissent.
Mouvement des réfs. Les commits aboutissent sur main (le tronc) plusieurs fois par jour, directement ou par branches d’un à deux jours de vie ; les fonctions inachevées sont masquées par des interrupteurs de fonctionnalité, non par des branches.
Adéquation. Exige trois critères réunis : couverture de tests élevée, CI rapide, discipline d’équipe. Sans eux, le modèle dégénère en workflow centralisé sans relecture — le pire des deux.
Les caractérisations qui précèdent tiennent dans un tableau à critères fermés : chaque case se vérifie comme vrai ou faux, ce qui rend le choix argumentable plutôt qu’opinionnel.
| Modèle | Branches permanentes | Relecture avant fusion | Déclencheur de déploiement | Coût de coordination |
|---|---|---|---|---|
| Centralisé | main |
Non | Poussée sur main |
Très faible |
| Feature branch | main |
Oui (PR) | Choix explicite | Faible |
| Gitflow | main, develop |
Oui | Fusion dans main via release/* |
Élevé |
| GitHub Flow | main |
Oui (PR) | Fusion dans main |
Faible |
| Forking | main (amont) |
Oui (PR depuis le fork) | Au bon vouloir du mainteneur | Moyen (double synchronisation) |
| Trunk-based | main |
Oui, continue | Chaque fusion | Élevé (tests, interrupteurs) |
Deux correspondances concluent la comparaison : un projet open source accueillant des contributeurs extérieurs relève du forking, sans discussion ; une organisation qui livre plusieurs fois par jour et dispose des tests correspondants relève du trunk-based. Entre ces deux pôles, feature branch et GitHub Flow couvrent l’essentiel des situations de cours et de produit ; Gitflow répond à un rythme de versions numérotées ; le centralisé reste cantonné à l’essai personnel ou au binôme de dépannage.
Intégration continue. Chaque événement de la forge (poussée, pull request) déclenche une pipeline : compilation, tests, linteurs. Deux mécanismes méritent d’être nommés, car la CI est souvent présentée comme une magie : un déclencheur — la forge appelle l’exécuteur de la pipeline sur l’événement — et une barrière — le résultat alimente la protection de branche côté serveur, qui refuse la fusion tant que la pipeline est rouge.
Le cours lui-même fonctionne sur ce modèle : le document que vous lisez est produit par une pipeline qui le rend à chaque poussée ; une leçon qui casse le rendu n’atteint pas le site. La vérification y est automatisée et bloquante — exactement ce que la CI signifie pour du code.
Livraison continue. Une fois la CI verte, la CD déploie selon la règle choisie dans le modèle (chaque fusion, chaque tag). La CD présuppose la CI : déployer sans vérification exécutée, c’est déployer plus vite ses propres erreurs.
Le slogan fondateur de la CI — test early, fail fast, fix quickly — n’est pas une incitation mais une définition d’un rythme : tester tôt, c’est exécuter les vérifications à chaque intégration et non en fin de cycle ; échouer vite, c’est garder une pipeline assez courte pour que la faute reste imputable à la dernière contribution ; corriger aussitôt, c’est considérer un état rouge comme bloquant, car une intégration cassée masque la suivante.
Une pull request n’est relisible que si elle dit ce qu’elle fait. Le gabarit — le template de la forge, un fichier PULL_REQUEST_TEMPLATE.md prérempli dans chaque nouvelle demande — borne la description à quatre rubriques :
La relecture s’appuie sur une checklist fermée, chaque item se décidant à la seule lecture de la demande :
reset --hard, clean -f, push forcé) sans justification écrite dans la description (Les bases de Git, section « Commandes destructrices »).Cette checklist constitue la partie revue de la grille de l’exercice final ; les étapes d’exécution de la consigne en constituent l’autre partie : relire son binôme avec ces critères, c’est déjà appliquer une moitié du barème.
La fusion crée un commit à deux parents qui devient la nouvelle position de la réf cible ; les commits fusionnés conservent leurs empreintes. C’est l’opération vérifiée dans Les bases de Git (section « Branches et fusions ») par la lecture de l’objet : la ligne parent y apparaît deux fois. L’option --no-ff impose cette trace de groupe même lorsque Git pourrait s’en passer — elle répond à la question « cette branche a-t-elle existé ? » longtemps après sa suppression.
Le rebassage ne crée aucun commit de fusion. Git détermine le dernier état commun aux deux branches, extrait les commits propres à la branche rebasée sous forme de correctifs, déplace la réf sur la nouvelle base, puis rejoue les correctifs un par un. Chaque commit est recréé : même message, mêmes modifications, mais parent différent — donc empreinte différente. La conséquence se lit directement dans la section « Travailler à plusieurs » de Les bases de Git : une branche rebasée ne contient plus l’histoire que le distant connaît déjà, et le refus non-fast-forward protège précisément celle-ci. Le squash proposé par les forges généralise ce mouvement : toute la contribution devient un correctif unique rejoué sur main, qui garde un historique linéaire.
Le choix employé dans ce cours se borne à trois règles :
main ;--no-ff ou squash), décidé sur la forge ;git rebase -i (nettoyage interactif de l’historique local) est nommé ici, non enseigné : il appartient à un autre moment du cursus.GitHub CLI (gh) n’est ni un client Git ni une couche magique : c’est un client de l’API de la forge. Les opérations qu’il accomplit sont celles de l’interface web — ouvrir une pull request, consulter une pipeline, créer un dépôt — exécutées par appels HTTP, donc scriptables. Le dépôt lui-même reste régi par Git seul : gh ne touche l’arbre de travail que par délégation, en invoquant les commandes vues dans la leçon précédente.
Deux précisions. gh pr checkout est un verbe de la forge qui récupère et affiche la branche d’une pull request par l’intermédiaire de Git ; il n’a rien à voir avec l’ancienne commande git checkout que ce cours a retirée de son vocabulaire. Et gh est un outil distinct de Git : sa disponibilité dans un environnement se vérifie par gh --version, sans présumer de sa présence.
Exercice : circuit complet d’une contribution
Sur une forge, forkez un petit dépôt, clonez votre fork, déclarez le dépôt d’origine sous le nom upstream, synchronisez votre branche principale, puis proposez une contribution par pull request. Si la forge n’est pas disponible au moment du rendu, la simulation ci-dessous produit exactement les mêmes objets et les mêmes échanges entre distants.
Simulation. Quatre dépôts dans /tmp/git-wf : un amont nu, son fork nu, le clone du mainteneur, le clone du contributeur. La première cellule définit les variables WF, UPSTREAM, FORK, SEED, CONTRIB : en cas de réexécution isolée, réexécuter-la d’abord.
L’amont est créé et pourvu d’un contenu initial par le mainteneur :
Initialized empty Git repository in /tmp/git-wf/upstream.git/
Cloning into '/tmp/git-wf/seed'...
warning: You appear to have cloned an empty repository.
done.
[main (root-commit) 178dcb3] docs: initialiser le dépôt amont
1 file changed, 1 insertion(+)
create mode 100644 README.md
Enumerating objects: 3, done.
Counting objects: 33% (1/3)
Counting objects: 66% (2/3)
Counting objects: 100% (3/3)
Counting objects: 100% (3/3), done.
Writing objects: 33% (1/3)
Writing objects: 66% (2/3)
Writing objects: 100% (3/3)
Writing objects: 100% (3/3), 256 bytes | 256.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To /tmp/git-wf/upstream.git
* [new branch] main -> main
branch 'main' set up to track 'origin/main'.
Le fork est un clone nu de l’amont ; le contributeur clone son fork et déclare l’amont en second distant :
Cloning into bare repository '/tmp/git-wf/fork.git'...
done.
Cloning into '/tmp/git-wf/contrib'...
done.
origin /tmp/git-wf/fork.git (fetch)
origin /tmp/git-wf/fork.git (push)
upstream /tmp/git-wf/upstream.git (fetch)
upstream /tmp/git-wf/upstream.git (push)
L’amont pendant que le contributeur a le dos tourné :
[main 821c09b] docs: préciser le circuit des contributions
1 file changed, 1 insertion(+)
Enumerating objects: 5, done.
Counting objects: 20% (1/5)
Counting objects: 40% (2/5)
Counting objects: 60% (3/5)
Counting objects: 80% (4/5)
Counting objects: 100% (5/5)
Counting objects: 100% (5/5), done.
Delta compression using up to 32 threads
Compressing objects: 50% (1/2)
Compressing objects: 100% (2/2)
Compressing objects: 100% (2/2), done.
Writing objects: 33% (1/3)
Writing objects: 66% (2/3)
Writing objects: 100% (3/3)
Writing objects: 100% (3/3), 338 bytes | 338.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To /tmp/git-wf/upstream.git
178dcb3..821c09b main -> main
Le contributeur synchronise son main avec fetch puis rebase — les deux gestes érigés en norme dans Les bases de Git :
remote: Enumerating objects: 5, done.
remote: Counting objects: 20% (1/5)
remote: Counting objects: 40% (2/5)
remote: Counting objects: 60% (3/5)
remote: Counting objects: 80% (4/5)
remote: Counting objects: 100% (5/5)
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 50% (1/2)
remote: Compressing objects: 100% (2/2)
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Unpacking objects: 33% (1/3)
Unpacking objects: 66% (2/3)
Unpacking objects: 100% (3/3)
Unpacking objects: 100% (3/3), 318 bytes | 318.00 KiB/s, done.
From /tmp/git-wf/upstream
* [new branch] main -> upstream/main
Successfully rebased and updated refs/heads/main.
Puis il travaille sur une branche dédiée et la pousse vers son fork, jamais vers l’amont où il n’a pas de droits :
Switched to a new branch 'contribution/atelier'
[contribution/atelier 5abfeba] docs: ajouter la contribution de l'atelier
1 file changed, 1 insertion(+)
create mode 100644 contributions/atelier.md
Enumerating objects: 9, done.
Counting objects: 11% (1/9)
Counting objects: 22% (2/9)
Counting objects: 33% (3/9)
Counting objects: 44% (4/9)
Counting objects: 55% (5/9)
Counting objects: 66% (6/9)
Counting objects: 77% (7/9)
Counting objects: 88% (8/9)
Counting objects: 100% (9/9)
Counting objects: 100% (9/9), done.
Delta compression using up to 32 threads
Compressing objects: 20% (1/5)
Compressing objects: 40% (2/5)
Compressing objects: 60% (3/5)
Compressing objects: 80% (4/5)
Compressing objects: 100% (5/5)
Compressing objects: 100% (5/5), done.
Writing objects: 14% (1/7)
Writing objects: 28% (2/7)
Writing objects: 42% (3/7)
Writing objects: 57% (4/7)
Writing objects: 71% (5/7)
Writing objects: 85% (6/7)
Writing objects: 100% (7/7)
Writing objects: 100% (7/7), 724 bytes | 724.00 KiB/s, done.
Total 7 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To /tmp/git-wf/fork.git
* [new branch] contribution/atelier -> contribution/atelier
branch 'contribution/atelier' set up to track 'origin/contribution/atelier'.
La pull request est l’interface de la forge ; son effet côté mainteneur se réduit à deux gestes connus : récupérer la branche du fork, la fusionner sous une trace explicite.
remote: Enumerating objects: 5, done.
remote: Counting objects: 20% (1/5)
remote: Counting objects: 40% (2/5)
remote: Counting objects: 60% (3/5)
remote: Counting objects: 80% (4/5)
remote: Counting objects: 100% (5/5)
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 33% (1/3)
remote: Compressing objects: 66% (2/3)
remote: Compressing objects: 100% (3/3)
remote: Compressing objects: 100% (3/3), done.
remote: Total 4 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Unpacking objects: 25% (1/4)
Unpacking objects: 50% (2/4)
Unpacking objects: 75% (3/4)
Unpacking objects: 100% (4/4)
Unpacking objects: 100% (4/4), 398 bytes | 398.00 KiB/s, done.
From /tmp/git-wf/fork
* branch contribution/atelier -> FETCH_HEAD
Merge made by the 'ort' strategy.
contributions/atelier.md | 1 +
1 file changed, 1 insertion(+)
create mode 100644 contributions/atelier.md
Enumerating objects: 6, done.
Counting objects: 16% (1/6)
Counting objects: 33% (2/6)
Counting objects: 50% (3/6)
Counting objects: 66% (4/6)
Counting objects: 83% (5/6)
Counting objects: 100% (6/6)
Counting objects: 100% (6/6), done.
Delta compression using up to 32 threads
Compressing objects: 25% (1/4)
Compressing objects: 50% (2/4)
Compressing objects: 75% (3/4)
Compressing objects: 100% (4/4)
Compressing objects: 100% (4/4), done.
Writing objects: 20% (1/5)
Writing objects: 40% (2/5)
Writing objects: 60% (3/5)
Writing objects: 80% (4/5)
Writing objects: 100% (5/5)
Writing objects: 100% (5/5), 511 bytes | 511.00 KiB/s, done.
Total 5 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
To /tmp/git-wf/upstream.git
821c09b..a64e981 main -> main
Critères de validation. L’exercice est reçu si trois observations tiennent sur l’amont.
* a64e981 (HEAD -> main, origin/main) Fusion de la contribution atelier depuis le fork
|\
| * 5abfeba docs: ajouter la contribution de l'atelier
|/
* 821c09b docs: préciser le circuit des contributions
* 178dcb3 docs: initialiser le dépôt amont
Le dernier commit de main porte deux parents — la fusion de la contribution est tracée ; le fichier contributions/atelier.md existe dans l’amont alors que son auteur n’y a jamais écrit ; et la synchronisation du contributeur n’a produit aucun commit de fusion, le rebase ayant suffi. Ces trois faits sont exactement ce qu’une forge montre sur la page d’une pull request acceptée.
Exercice : le binôme et la même ligne
Dans le dépôt que l’enseignement fournit — vous y avez un accès en écriture à deux —, indépendamment de son contenu. Ce dépôt est indiqué dans l’annonce de séance ; à défaut, prenez le dépôt que vous avez poussé à l’exercice de la leçon précédente et invitez votre binôme en écriture :
contributions/README.md doit exister sur main avec une ligne Dernière mise à jour : …. Si le dépôt fourni ne le contient pas, l’un des deux l’y ajoute et le pousse ; l’autre synchronise.main : git switch -c <pseudo>-contrib.contributions/<pseudo>.md décrivant une chose apprise de la séquence Git, puis modifie de son côté la ligne Dernière mise à jour : … du README pour y porter son pseudo. Validation, poussée, pull request ouverte avec le gabarit de la section Gabarit de pull request et checklist de revue.git pull origin main (après avoir déclaré git config --global pull.rebase false, cf. §8) — les marqueurs étudiés dans Les bases de Git (section « Conflits de fusion ») apparaissent dans votre copie de travail.git add pour marquer la résolution, commiter la fusion.git restore <fichier>, puis git switch remet en place — la grammaire moderne établie par la leçon précédente.Corrigé de référence. La simulation ci-dessous produit les mêmes objets, les mêmes refus et les mêmes marqueurs que votre binôme constatera sur la forge : un serveur nu, deux clones, un fichier partagé journal.md dont la ligne d’en-tête est modifiée des deux côtés. La première cellule définit SERVEUR2 et les deux clones ; en réexécution isolée, repartir d’elle.
SERVEUR2=/tmp/git-wf-serveur.git
rm -rf $SERVEUR2 /tmp/git-wf-a /tmp/git-wf-b
git config --global init.defaultBranch main
git config --global user.name "Atelier Git"
git config --global user.email "atelier.git@example.invalid"
git config --global pull.rebase false
git init --bare $SERVEUR2
git clone $SERVEUR2 /tmp/git-wf-a
cd /tmp/git-wf-a
printf 'Journal du binôme.\n\nDernière mise à jour : atelier\n' > journal.md
git add journal.md
git commit -m "docs: initialiser le journal partagé"
git push -u origin main
git clone $SERVEUR2 /tmp/git-wf-bLa simulation suppose un pull à fusion explicite ; depuis Git 2.27, ce comportement doit être déclaré (pull.rebase false), sans quoi Git refuse d’arbitrer entre fusion et rebassage sur des branches divergentes.
Premier étudiant : il modifie la ligne et pousse.
[main badd308] docs: journal mis à jour par A
1 file changed, 1 insertion(+), 1 deletion(-)
Enumerating objects: 5, done.
Counting objects: 20% (1/5)
Counting objects: 40% (2/5)
Counting objects: 60% (3/5)
Counting objects: 80% (4/5)
Counting objects: 100% (5/5)
Counting objects: 100% (5/5), done.
Delta compression using up to 32 threads
Compressing objects: 50% (1/2)
Compressing objects: 100% (2/2)
Compressing objects: 100% (2/2), done.
Writing objects: 33% (1/3)
Writing objects: 66% (2/3)
Writing objects: 100% (3/3)
Writing objects: 100% (3/3), 328 bytes | 328.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To /tmp/git-wf-serveur.git
06c997d..badd308 main -> main
Second étudiant, parti de l’état antérieur, fait de même dans son clone :
[main 838ce95] docs: journal mis à jour par B
1 file changed, 1 insertion(+), 1 deletion(-)
Sa poussée est refusée : le clone b ignore le commit de a et sa proposition l’effacerait. La cellule est marquée error: true pour que le refus reste visible au rendu :
To /tmp/git-wf-serveur.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to '/tmp/git-wf-serveur.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
: 1
L’étudiant qui pull récupère alors le commit de a et tente la fusion implicite du pull : impossible de décider automatiquement, la même ligne a bougé des deux côtés. La cellule bute sur le conflit, et le rendu conserve le diagnostic :
remote: Enumerating objects: 5, done.
remote: Counting objects: 20% (1/5)
remote: Counting objects: 40% (2/5)
remote: Counting objects: 60% (3/5)
remote: Counting objects: 80% (4/5)
remote: Counting objects: 100% (5/5)
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 50% (1/2)
remote: Compressing objects: 100% (2/2)
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Unpacking objects: 33% (1/3)
Unpacking objects: 66% (2/3)
Unpacking objects: 100% (3/3)
Unpacking objects: 100% (3/3), 308 bytes | 308.00 KiB/s, done.
From /tmp/git-wf-serveur
* branch main -> FETCH_HEAD
06c997d..badd308 main -> origin/main
Auto-merging journal.md
CONFLICT (content): Merge conflict in journal.md
Automatic merge failed; fix conflicts and then commit the result.
: 1
Le fichier en conflit contient désormais les deux régions étudiées dans Les bases de Git — la version du commit courant et la version entrante — séparées par ======= :
Journal du binôme.
<<<<<<< HEAD
Dernière mise à jour : étudiant B
=======
Dernière mise à jour : étudiant A
>>>>>>> badd3086a03f3ad321183e418245bc3444be1300
Remarque. Le style par défaut de Git montre deux candidats ; git config merge.conflictstyle diff3 (documenté dans git-config(1)) ajoute la base commune entre ||||||| et =======.
Résoudre, ce n’est pas choisir un camp : c’est produire un contenu que les deux auteurs acceptent. Le journal garde les deux mentions, ordonnées dans le temps :
[main a846b73] Merge branch 'main' of /tmp/git-wf-serveur
Enumerating objects: 10, done.
Counting objects: 10% (1/10)
Counting objects: 20% (2/10)
Counting objects: 30% (3/10)
Counting objects: 40% (4/10)
Counting objects: 50% (5/10)
Counting objects: 60% (6/10)
Counting objects: 70% (7/10)
Counting objects: 80% (8/10)
Counting objects: 90% (9/10)
Counting objects: 100% (10/10)
Counting objects: 100% (10/10), done.
Delta compression using up to 32 threads
Compressing objects: 25% (1/4)
Compressing objects: 50% (2/4)
Compressing objects: 75% (3/4)
Compressing objects: 100% (4/4)
Compressing objects: 100% (4/4), done.
Writing objects: 16% (1/6)
Writing objects: 33% (2/6)
Writing objects: 50% (3/6)
Writing objects: 66% (4/6)
Writing objects: 83% (5/6)
Writing objects: 100% (6/6)
Writing objects: 100% (6/6), 633 bytes | 633.00 KiB/s, done.
Total 6 (delta 1), reused 0 (delta 0), pack-reused 0 (from 0)
To /tmp/git-wf-serveur.git
badd308..a846b73 main -> main
La fusion est achevée ; son tracé se vérifie dans le graphe :
Barème. La grille d’évaluation combine les six critères de revue (§4) et les étapes 2–5 de la consigne ; chaque ligne du barème se décide à la lecture de la pull request ou du dépôt, sans interprétation.
| Critère | Preuve attendue | Points |
|---|---|---|
| Titre de la PR conforme à Conventional Commits | Page de la PR | 1 |
| Gabarit complété : les quatre rubriques | Page de la PR | 2 |
| Un fichier touché, une ligne de justification | Rubrique Modifications | 1 |
| Vérification énoncée et réexécutable | Rubrique Vérification | 1 |
| Pipeline de CI verte | Affichage de la CI sur la PR | 1 |
| Relecture par l’autre : commentaire puis approbation | Fil de discussion de la PR | 2 |
| Conflit reproduit puis résolu, marqueur absent du fichier final | Contenu du README après fusion | 2 |
Fusion tracée sur main |
git log --graph |
2 |
Total : douze points. Le barème accorde délibérément plus de poids à la trace de relecture et à la résolution du conflit qu’à la dextérité sur la ligne de commande : c’est le comportement d’équipe qui est évalué, non la vitesse d’exécution.
Pour éprouver physiquement la chorégraphie de Gitflow, sans forge. Appliquez chaque mouvement dans le dépôt de travail cloné du dépôt nu et vérifiez, après chaque phase, ce que montre git log --graph --oneline. Les trois mouvements à nommer : une branche de release naît de develop et retourne aux deux branches permanentes ; un hotfix naît de main et retourne aux deux ; chaque retour laisse un commit de fusion et, sur main, un tag.
# Bloc non exécuté par le rendu — patron de commandes.
# installation en deux temps : (1) dépôt nu central
git config --global init.defaultBranch main
git config --global user.name "Atelier Git"
git config --global user.email "atelier.git@example.invalid"
git init --bare /tmp/git-wf-bonus.git
# (2) clone de travail — origin est donc définie
git clone /tmp/git-wf-bonus.git /tmp/git-wf-bonus
cd /tmp/git-wf-bonus
# deux branches permanentes
git switch -c develop
git push -u origin develop
# une fonctionnalité complète
git switch -c feature/connexion develop
# ... validations sur la branche ...
git switch develop
git merge --no-ff feature/connexion
# une release
git switch -c release/1.0 develop
echo "Notes de version 1.0" > RELEASE-NOTES.md; git add RELEASE-NOTES.md; git commit -m "release: préparer la 1.0"
git switch main
git merge --no-ff release/1.0
git tag -a v1.0 -m "Version 1.0"
git switch develop
git merge --no-ff release/1.0
# un correctif urgent, né de main
git switch main
git switch -c hotfix/1.0.1
echo "Correctif 1.0.1 : régression d'authentification corrigée" >> RELEASE-NOTES.md; git add RELEASE-NOTES.md; git commit -m "fix: corriger la régression d'authentification"
git switch main
git merge --no-ff hotfix/1.0.1
git tag -a v1.0.1 -m "Correctif 1.0.1"
git switch develop
git merge --no-ff hotfix/1.0.1Critère de réussite : les deux tags v1.0 et v1.0.1 portent sur main ; develop contient les fusions de la release et du hotfix ; aucun commit direct de fonctionnalité n’apparaît sur main en dehors de ces fusions.
Un workflow produit ses effets à trois conditions, chacune vérifiable : formalisation dans le README du projet — les quatre objectifs de ce début de leçon, transcrits en règles d’équipe ; protection de la branche principale côté serveur — push direct interdit, relecture et pipeline exigées ; vérification automatisée à chaque pull request et à chaque fusion, avec la commande qui convient au projet, dans une liste close : make test, mvn verify, pytest, ou l’équivalent de votre outillage. Un workflow ni écrit ni appliqué cesse d’être un workflow : il devient une habitude que l’on croit collective.
Parmi les six modèles, la réponse par défaut du cours tient en une phrase : le rythme de GitHub Flow avec la discipline de relecture du feature branch workflow, c’est-à-dire des branches courtes et des pull requests relues. Le forking s’impose dès que les contributeurs n’ont pas de droits ; Gitflow répond à un rythme de versions numérotées ; le trunk-based suppose une maturité de CI que l’on annonce rarement acquis d’avance.
Ressources. Six lectures, toutes citées par le corpus du cours ; la dernière est un reflexe de terminal, non un site.
gh installée dans l’image du cours se relève par gh --version.gitglossary(7), et sa porte d’entrée git(1).E. Bruno - Workflows de collaboration Git : modèles de branches, revue de code et intégration continue