Workflows collaboratifs#

Git est un outil remarquablement flexible : il ne prescrit aucun workflow particulier. Contrairement à des systèmes comme SVN ou le modèle centralisé impose une façon de travailler, Git laisse les équipes libres de définir leurs propres conventions de collaboration. Cette liberté est à la fois une force et un piège : sans workflow clairement établi, une équipe risque de sombrer dans le chaos des branches orphelines, des conflits à répétition et des historiques illisibles.

Ce chapitre présente les workflows les plus répandus dans l’industrie du logiciel. Chacun répond à des besoins spécifiques en termes de taille d’équipe, de fréquence de déploiement et de rigueur dans la gestion des versions. L’objectif n’est pas de désigner un workflow « supérieur » aux autres, mais de comprendre les compromis de chacun pour choisir celui qui convient le mieux à un contexte donné.

Hide code cell source

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import numpy as np

Workflow centralisé#

Définition 35 (Workflow centralisé)

Dans le workflow centralisé, tous les développeurs travaillent directement sur une seule branche partagée, généralement main. Chaque développeur clone le dépôt, effectue ses modifications localement, puis pousse (push) ses commits directement sur main. Il n’y a pas de branches de fonctionnalité, pas de pull requests, pas de revue de code formelle. C’est le modèle le plus proche du fonctionnement classique de SVN.

Ce workflow a le mérite de la simplicité. Un seul flux linéaire, aucune stratégie de branchement à comprendre, aucune politique de fusion à respecter. Pour un développeur seul ou une équipe de deux ou trois personnes travaillant sur un projet simple, il peut être tout à fait suffisant.

Mais ses limites apparaissent rapidement dès que l’équipe grandit :

  • Conflits fréquents. Plusieurs développeurs poussant sur la même branche simultanément génèrent inévitablement des conflits.

  • Branche principale instable. Un commit défectueux poussé sur main affecte immédiatement toute l’équipe.

  • Absence de revue de code. Rien n’empèche du code de mauvaise qualité d’atteindre la branche principale.

Remarque 34

Le workflow centralisé reste pertinent pour les projets personnels, les prototypes rapides ou les très petites équipes où la communication informelle compense l’absence de processus formel. Dès qu’une équipe dépasse trois personnes ou que le projet nécessite une certaine stabilité, il est fortement recommandé de migrer vers un workflow basé sur les branches.

Feature Branch workflow#

Définition 36 (Feature Branch workflow)

Le Feature Branch workflow repose sur un principe simple : chaque nouvelle fonctionnalité, chaque correctif, chaque modification est developpée dans sa propre branche, isolée de main. Une fois le travail terminé et révisé, la branche est fusionnée dans main via une pull request (ou merge request). La branche main ne reçoit jamais de commit direct ; elle n’évolue que par fusion de branches de fonctionnalité validées.

Le cycle de travail se déroule ainsi :

  1. Créer une branche à partir de main : git switch -c feature/ma-fonctionnalite

  2. Développer sur cette branche avec des commits réguliers et atomiques.

  3. Pousser la branche sur le dépôt distant : git push -u origin feature/ma-fonctionnalite

  4. Ouvrir une pull request pour demander la revue de code par un ou plusieurs collègues.

  5. Réviser et itérer : les relecteurs commentent, l’auteur corrige, jusqu’à approbation.

  6. Fusionner la pull request dans main.

  7. Supprimer la branche de fonctionnalité, devenue inutile.

Remarque 35

Le Feature Branch workflow est de loin le workflow le plus répandu dans le développement logiciel moderne. Il est adopté par défaut sur GitHub, GitLab et Bitbucket, qui fournissent tous une interface dédiée aux pull requests. Ce workflow offre un excellent équilibre entre simplicité et rigueur : il introduit la revue de code et l’isolation des fonctionnalités sans imposer la complexité d’un modèle comme Gitflow.

Hide code cell source

fig, ax = plt.subplots(figsize=(14, 6))
ax.set_xlim(-0.5, 14)
ax.set_ylim(-2.5, 4.5)
ax.axis("off")
ax.set_title("Feature Branch workflow", fontsize=15, fontweight="bold", pad=18)

# Couleurs
c_main = "#2c3e50"
c_feat_a = "#e74c3c"
c_feat_b = "#3498db"
c_merge = "#27ae60"

# --- Branche main (ligne horizontale) ---
main_y = 0
main_commits_x = [0, 2, 8, 11, 13]
ax.plot([0, 13], [main_y, main_y], color=c_main, lw=3, zorder=2)
for x in main_commits_x:
    ax.plot(x, main_y, "o", color=c_main, markersize=14, zorder=4)
    ax.plot(x, main_y, "o", color="white", markersize=8, zorder=5)

# Etiquettes commits main
main_labels = ["A", "B", "F", "G", "H"]
for x, lab in zip(main_commits_x, main_labels):
    ax.text(x, main_y - 0.55, lab, ha="center", va="center",
            fontsize=9, fontweight="bold", color=c_main)

# Etiquette branche main
ax.annotate("main", xy=(13, main_y), xytext=(13.5, main_y + 0.5),
            fontsize=11, fontweight="bold", color="white",
            bbox=dict(boxstyle="round,pad=0.3", facecolor=c_main, edgecolor="none"),
            arrowprops=dict(arrowstyle="-", color=c_main, lw=1.5))

# --- Feature A (au-dessus) ---
feat_a_y = 2.2
feat_a_x = [3.5, 5.5, 7]
# Branche depuis B (x=2)
ax.plot([2, feat_a_x[0]], [main_y, feat_a_y], color=c_feat_a, lw=2, ls="--", zorder=2)
ax.plot([feat_a_x[0], feat_a_x[-1]], [feat_a_y, feat_a_y], color=c_feat_a, lw=2.5, zorder=2)
for x in feat_a_x:
    ax.plot(x, feat_a_y, "o", color=c_feat_a, markersize=12, zorder=4)
    ax.plot(x, feat_a_y, "o", color="white", markersize=6, zorder=5)

# Merge vers main (x=8)
ax.plot([feat_a_x[-1], 8], [feat_a_y, main_y], color=c_merge, lw=2.5, ls="--", zorder=2)
ax.plot(8, main_y, "D", color=c_merge, markersize=12, zorder=6)

feat_a_labels = ["C", "D", "E"]
for x, lab in zip(feat_a_x, feat_a_labels):
    ax.text(x, feat_a_y + 0.5, lab, ha="center", va="center",
            fontsize=9, fontweight="bold", color=c_feat_a)

ax.text(5.5, feat_a_y + 1.2, "feature/login", ha="center", va="center",
        fontsize=10, fontweight="bold", color="white",
        bbox=dict(boxstyle="round,pad=0.3", facecolor=c_feat_a, edgecolor="none"))

# --- Feature B (en-dessous) ---
feat_b_y = -1.8
feat_b_x = [4.5, 6.5, 9, 10]
# Branche depuis B (x=2)
ax.plot([2, feat_b_x[0]], [main_y, feat_b_y], color=c_feat_b, lw=2, ls="--", zorder=2)
ax.plot([feat_b_x[0], feat_b_x[-1]], [feat_b_y, feat_b_y], color=c_feat_b, lw=2.5, zorder=2)
for x in feat_b_x:
    ax.plot(x, feat_b_y, "o", color=c_feat_b, markersize=12, zorder=4)
    ax.plot(x, feat_b_y, "o", color="white", markersize=6, zorder=5)

# Merge vers main (x=11)
ax.plot([feat_b_x[-1], 11], [feat_b_y, main_y], color=c_merge, lw=2.5, ls="--", zorder=2)
ax.plot(11, main_y, "D", color=c_merge, markersize=12, zorder=6)

feat_b_labels = ["I", "J", "K", "L"]
for x, lab in zip(feat_b_x, feat_b_labels):
    ax.text(x, feat_b_y - 0.55, lab, ha="center", va="center",
            fontsize=9, fontweight="bold", color=c_feat_b)

ax.text(7.25, feat_b_y - 1.3, "feature/panier", ha="center", va="center",
        fontsize=10, fontweight="bold", color="white",
        bbox=dict(boxstyle="round,pad=0.3", facecolor=c_feat_b, edgecolor="none"))

# Annotations merge
ax.annotate("merge\n(PR)", xy=(8, main_y), xytext=(8, main_y + 1.0),
            fontsize=8, ha="center", color=c_merge, fontweight="bold",
            arrowprops=dict(arrowstyle="->", color=c_merge, lw=1.2))
ax.annotate("merge\n(PR)", xy=(11, main_y), xytext=(11, main_y - 1.1),
            fontsize=8, ha="center", color=c_merge, fontweight="bold",
            arrowprops=dict(arrowstyle="->", color=c_merge, lw=1.2))

# Légende
legend_elements = [
    mpatches.Patch(facecolor=c_main, label="main"),
    mpatches.Patch(facecolor=c_feat_a, label="feature/login"),
    mpatches.Patch(facecolor=c_feat_b, label="feature/panier"),
    mpatches.Patch(facecolor=c_merge, label="merge (pull request)"),
]
ax.legend(handles=legend_elements, loc="upper left", fontsize=9,
          frameon=True, fancybox=True, shadow=True)

plt.show()
_images/fcd795983ee28e8bbefae82d7b30432b8497c9faf0d6c91eac1c704f151419b5.png

Le diagramme illustre deux branches de fonctionnalité évoluant en parallèle. Chacune part de main, accumule ses propres commits, puis est fusionnée via une pull request (losanges verts). L’isolation garantit que le travail sur le login ne perturbe pas celui sur le panier, et réciproquement. La branche main reste stable car elle ne reçoit que du code révisé et approuvé.

Gitflow#

Définition 37 (Gitflow)

Gitflow est un modèle de branchement formalisé, propose par Vincent Driessen en 2010. Il définit cinq types de branches ayant chacune un rôle précis :

  • main : contient uniquement les versions de production. Chaque commit sur main correspond à une release.

  • develop : branche d’intégration où convergent toutes les fonctionnalités. C’est la base de travail quotidienne.

  • feature/* : branches de fonctionnalité. Elles partent de develop et y retournent une fois terminées.

  • release/* : branches de préparation d’une release. Elles partent de develop, reçoivent les derniers correctifs, puis sont fusionnées dans main et develop.

  • hotfix/* : branches de correctif urgent. Elles partent de main, corrigent un bug critique en production, puis sont fusionnées dans main et develop.

Le cycle de vie Gitflow suit un schéma précis :

  • Le développement courant se fait sur des branches feature/* créées à partir de develop.

  • Quand suffisamment de fonctionnalités sont prêtes, on crée une branche release/* à partir de develop pour stabiliser la version : corrections de bugs, mise à jour de la documentation, ajustement des numéros de version.

  • Une fois stabilisée, la branche de release est fusionnée dans main (avec un tag de version) et dans develop (pour réintegrer les correctifs).

  • Si un bug critique est découvert en production, on crée une branche hotfix/* à partir de main, on corrige, puis on fusionne dans main et develop.

Remarque 36

Gitflow est bien adapté aux projets avec des cycles de release planifiés (versions trimestrielles, mensuelles, etc.) et où plusieurs versions doivent être maintenues en parallèle. Cependant, sa complexité est souvent excessive pour les projets en déploiement continu, ou chaque commit sur main est potentiellement déployé en production. Dans ce cas, la distinction entre main et develop perd son sens, et les branches de release deviennent superflues. Vincent Driessen lui-même a ajouté une note è son article original reconnaissant que Gitflow n’est pas toujours le bon choix.

Hide code cell source

fig, ax = plt.subplots(figsize=(16, 9))
ax.set_xlim(-1, 17)
ax.set_ylim(-3.5, 5)
ax.axis("off")
ax.set_title("Modèle Gitflow", fontsize=16, fontweight="bold", pad=20)

# Couleurs par type de branche
c_main = "#2c3e50"
c_develop = "#2980b9"
c_feature = "#27ae60"
c_release = "#e67e22"
c_hotfix = "#c0392b"

# Positions verticales des lignes
y_main = 3.5
y_develop = 1.5
y_feature = -0.8
y_release = 1.5    # même niveau, décalé à droite
y_hotfix = 3.5     # même niveau que main, décalé

# =============================================================
#  Branche main
# =============================================================
ax.plot([0, 16], [y_main, y_main], color=c_main, lw=4, zorder=2)
main_x = [0, 12, 15.5]
for x in main_x:
    ax.plot(x, y_main, "o", color=c_main, markersize=15, zorder=4)
    ax.plot(x, y_main, "o", color="white", markersize=8, zorder=5)

# Tags
for x, tag in zip([12, 15.5], ["v1.0", "v1.0.1"]):
    ax.text(x, y_main + 0.55, tag, ha="center", va="center", fontsize=8,
            fontweight="bold", color="white",
            bbox=dict(boxstyle="round,pad=0.25", facecolor="#8e44ad", edgecolor="none"))

ax.text(-0.9, y_main, "main", ha="center", va="center", fontsize=11,
        fontweight="bold", color="white",
        bbox=dict(boxstyle="round,pad=0.35", facecolor=c_main, edgecolor="none"))

# =============================================================
#  Branche develop
# =============================================================
ax.plot([0, 16], [y_develop, y_develop], color=c_develop, lw=3, zorder=2)
dev_x = [0, 2, 5, 7.5, 10, 13.5, 16]
for x in dev_x:
    ax.plot(x, y_develop, "o", color=c_develop, markersize=12, zorder=4)
    ax.plot(x, y_develop, "o", color="white", markersize=6, zorder=5)

ax.text(-0.9, y_develop, "develop", ha="center", va="center", fontsize=11,
        fontweight="bold", color="white",
        bbox=dict(boxstyle="round,pad=0.35", facecolor=c_develop, edgecolor="none"))

# =============================================================
#  Branches feature (en-dessous de develop)
# =============================================================
# Feature 1 : part de develop (x=2) et revient (x=5)
feat1_x = [3, 4]
ax.plot([2, feat1_x[0]], [y_develop, y_feature], color=c_feature, lw=2, ls="--", zorder=2)
ax.plot([feat1_x[0], feat1_x[-1]], [y_feature, y_feature], color=c_feature, lw=2.5, zorder=2)
ax.plot([feat1_x[-1], 5], [y_feature, y_develop], color=c_feature, lw=2, ls="--", zorder=2)
for x in feat1_x:
    ax.plot(x, y_feature, "o", color=c_feature, markersize=10, zorder=4)
    ax.plot(x, y_feature, "o", color="white", markersize=5, zorder=5)

ax.text(3.5, y_feature - 0.6, "feature/A", ha="center", fontsize=9,
        fontweight="bold", color="white",
        bbox=dict(boxstyle="round,pad=0.25", facecolor=c_feature, edgecolor="none"))

# Feature 2 : part de develop (x=5) et revient (x=7.5)
feat2_x = [5.8, 6.8]
ax.plot([5, feat2_x[0]], [y_develop, y_feature], color=c_feature, lw=2, ls="--", zorder=2)
ax.plot([feat2_x[0], feat2_x[-1]], [y_feature, y_feature], color=c_feature, lw=2.5, zorder=2)
ax.plot([feat2_x[-1], 7.5], [y_feature, y_develop], color=c_feature, lw=2, ls="--", zorder=2)
for x in feat2_x:
    ax.plot(x, y_feature, "o", color=c_feature, markersize=10, zorder=4)
    ax.plot(x, y_feature, "o", color="white", markersize=5, zorder=5)

ax.text(6.3, y_feature - 0.6, "feature/B", ha="center", fontsize=9,
        fontweight="bold", color="white",
        bbox=dict(boxstyle="round,pad=0.25", facecolor=c_feature, edgecolor="none"))

# =============================================================
#  Branche release (entre develop et main)
# =============================================================
y_rel = 2.5
rel_x = [8.5, 9.5, 11]
ax.plot([7.5, rel_x[0]], [y_develop, y_rel], color=c_release, lw=2, ls="--", zorder=2)
ax.plot([rel_x[0], rel_x[-1]], [y_rel, y_rel], color=c_release, lw=2.5, zorder=2)
for x in rel_x:
    ax.plot(x, y_rel, "o", color=c_release, markersize=10, zorder=4)
    ax.plot(x, y_rel, "o", color="white", markersize=5, zorder=5)

# Merge release -> main
ax.plot([rel_x[-1], 12], [y_rel, y_main], color=c_release, lw=2, ls="--", zorder=2)
# Merge release -> develop
ax.plot([rel_x[-1], 13.5], [y_rel, y_develop], color=c_release, lw=2, ls="--", zorder=2)

ax.text(9.5, y_rel + 0.5, "release/1.0", ha="center", fontsize=9,
        fontweight="bold", color="white",
        bbox=dict(boxstyle="round,pad=0.25", facecolor=c_release, edgecolor="none"))

# =============================================================
#  Branche hotfix (au-dessus de main)
# =============================================================
y_hot = 4.5
hot_x = [13.5, 14.5]
ax.plot([12, hot_x[0]], [y_main, y_hot], color=c_hotfix, lw=2, ls="--", zorder=2)
ax.plot([hot_x[0], hot_x[-1]], [y_hot, y_hot], color=c_hotfix, lw=2.5, zorder=2)
for x in hot_x:
    ax.plot(x, y_hot, "o", color=c_hotfix, markersize=10, zorder=4)
    ax.plot(x, y_hot, "o", color="white", markersize=5, zorder=5)

# Merge hotfix -> main
ax.plot([hot_x[-1], 15.5], [y_hot, y_main], color=c_hotfix, lw=2, ls="--", zorder=2)
# Merge hotfix -> develop
ax.plot([hot_x[-1], 16], [y_hot, y_develop], color=c_hotfix, lw=2, ls="--", zorder=2)

ax.text(14, y_hot + 0.45, "hotfix/1.0.1", ha="center", fontsize=9,
        fontweight="bold", color="white",
        bbox=dict(boxstyle="round,pad=0.25", facecolor=c_hotfix, edgecolor="none"))

# =============================================================
#  Légende
# =============================================================
legend_elements = [
    mpatches.Patch(facecolor=c_main, label="main (production)"),
    mpatches.Patch(facecolor=c_develop, label="develop (integration)"),
    mpatches.Patch(facecolor=c_feature, label="feature/* (fonctionnalites)"),
    mpatches.Patch(facecolor=c_release, label="release/* (stabilisation)"),
    mpatches.Patch(facecolor=c_hotfix, label="hotfix/* (correctif urgent)"),
]
ax.legend(handles=legend_elements, loc="lower left", fontsize=9,
          frameon=True, fancybox=True, shadow=True, ncol=2)

plt.show()
_images/a1f58b720902cbb87a7e1b223c4dbd3ce900f1a597232be5784b7887f82bca2d.png

Le diagramme met en évidence le flux unidirectionnel du code dans Gitflow. Les fonctionnalités naissent en bas (branches feature/*), remontent vers develop, puis transitent par une branche release/* avant d’atteindre main en haut. Les hotfixes suivent un chemin d’urgence direct depuis main, avec une réintegration dans develop pour ne pas perdre le correctif.

Trunk-based development#

Définition 38 (Trunk-based development)

Le trunk-based development (développement sur le tronc) est un workflow où tous les développeurs intègrent leur travail directement dans une branche unique, généralement main, appelée le tronc (trunk). Les branches, quand elles existent, sont extrêmement éphémères : elles vivent quelques heures, rarement plus d’une journée. L’intégration continue (CI) est exécutée à chaque commit sur le tronc, garantissant que la branche principale reste toujours dans un état déployable.

Le trunk-based development repose sur plusieurs pratiques complémentaires :

  • Branches à courte durée de vie. Une branche de fonctionnalité ne doit pas vivre plus de 24 heures. Cela force les développeurs à décomposer leur travail en petits incréments intégrables rapidement.

  • Feature flags. Les fonctionnalités en cours de développement sont masquées derrière des drapeaux de fonctionnalité (feature flags) qui permettent de les activer ou désactiver en production sans modifier le code. Cela permet de fusionner du code incomplet dans main sans affecter les utilisateurs.

  • Intégration continue rigoureuse. Chaque commit sur main déclenche une suite de tests automatisés. Si un test échoue, la correction est prioritaire.

Exemple 10 (Feature flags)

Imaginons une application web à laquelle on ajoute un nouveau système de recommandations. Plutôt que de développer la fonctionnalité pendant trois semaines dans une branche isolée, le trunk-based development préconise d’intégrer le code jour après jour dans main, protégé par un feature flag :

if feature_flags.is_enabled("new_recommendations", user=current_user):
    show_new_recommendations(user=current_user)
else:
    show_classic_recommendations(user=current_user)

Le flag peut être activé pour 5% des utilisateurs pour un test A/B, puis progressivement étendu à 100% une fois la fonctionnalité validée.

Remarque 37

Le trunk-based development est le workflow adopté par Google, Meta (Facebook), Microsoft et d’autres grandes entreprises technologiques. Google, par exemple, gère un monorepo de plus de deux milliards de lignes de code ou des dizaines de milliers de developpeurs commitent directement sur le tronc. Ce workflow exige cependant une culture d’ingénierie mature : couverture de tests élevée, intégration continue rapide et fiable, revue de code systématique, et capacité à déployer et annuler rapidement. Sans ces prérequis, le trunk-based development peut rapidement devenir chaotique.

Forking workflow#

Définition 39 (Forking workflow)

Le forking workflow est un modèle où chaque contributeur fork (copie) le dépôt principal dans son propre espace de noms. Le contributeur travaille dans son fork — créant des branches, effectuant des commits — puis soumet une pull request de son fork vers le dépôt original (appele upstream). Les mainteneurs du projet révisent la pull request et décident de la fusionner ou non. A aucun moment le contributeur n’a besoin d’un accès en écriture au dépôt principal.

Ce workflow est le standard de facto pour les projets open source. Sur GitHub, forker un dépôt est une opération en un clic qui crée une copie complète du dépôt dans le compte de l’utilisateur. La configuration des remotes est la suivante :

  • origin : le fork personnel du contributeur (accès en lecture et écriture).

  • upstream : le dépòt original du projet (accès en lecture seule, en général).

Exemple 11 (Contribuer à un projet open source)

Voici le flux typique pour contribuer à un projet open source sur GitHub :

  1. Forker le dépôt via l’interface de GitHub.

  2. Cloner son fork : git clone https://github.com/mon-compte/projet.git

  3. Ajouter le remote upstream : git remote add upstream https://github.com/auteur/projet.git

  4. Créer une branche de fonctionnalité : git switch -c fix/correction-typo

  5. Développer, commiter, pousser vers son fork : git push origin fix/correction-typo

  6. Ouvrir une pull request depuis son fork vers le dépôt upstream.

  7. Synchroniser regulièrement avec upstream : git fetch upstream && git merge upstream/main

Remarque 38

Le forking workflow ajoute une couche d’isolation essentielle pour les projets open source. Les contributeurs externes n’ont pas besoin d’accès en écriture au dépôt principal, ce qui élimine tout risque de modification accidentelle ou malveillante. Les mainteneurs conservent un contrôle total sur ce qui est fusionné dans le projet. Ce modèle permet à des milliers de contributeurs de collaborer sur un même projet — le noyau Linux, par exemple, reçoit des contributions de plus de 15 000 développeurs via ce type de workflow.

Choisir son workflow#

Le choix d’un workflow dépend de plusieurs facteurs. Le tableau suivant propose un guide de décision rapide.

Critère

Workflow recommandé

Développeur solo ou équipe de 2-3 personnes

Feature Branch (simplicité avec un minimum de rigueur)

Equipe de taille moyenne (5-20 personnes)

Feature Branch ou Trunk-based selon la maturite CI/CD

Projet open source avec contributeurs externes

Forking workflow

Releases planifiées (v1.0, v1.1, v2.0, …)

Gitflow

Déploiement continu (plusieurs déploiements par jour)

Trunk-based development

Projet très simple ou prototype rapide

Centralisé (mais migrer vite vers Feature Branch)

Remarque 39

En pratique, de nombreuses équipes adoptent un workflow hybride qui emprunte des éléments à plusieurs modèles. Par exemple, une équipe peut utiliser le Feature Branch workflow avec la convention de nommage de Gitflow (feature/*, fix/*, docs/*) sans avoir de branche develop séparée. Ou encore, une équipe en trunk-based development peut autoriser des branches de quelques jours pour les refactorings majeurs. Le meilleur workflow est celui que toute l’équipe comprend et applique de facon cohérente. Un workflow sophistiqué mal compris est pire qu’un workflow simple bien suivi.

Hide code cell source

fig, ax = plt.subplots(figsize=(14, 7))
ax.set_xlim(0, 10)
ax.set_ylim(-0.5, 5.5)
ax.axis("off")
ax.set_title("Choisir son workflow : arbre de décision", fontsize=15,
             fontweight="bold", pad=18)

# Couleurs des blocs
c_question = "#34495e"
c_answer = "#2980b9"
c_result = "#27ae60"
c_arrow = "#7f8c8d"

def draw_box(ax, x, y, text, color, width=2.2, height=0.7, fontsize=9):
    """Dessine un rectangle arrondi avec du texte centré."""
    box = mpatches.FancyBboxPatch(
        (x - width / 2, y - height / 2), width, height,
        boxstyle="round,pad=0.15", facecolor=color, edgecolor="white",
        linewidth=1.5, zorder=4)
    ax.add_patch(box)
    ax.text(x, y, text, ha="center", va="center", fontsize=fontsize,
            fontweight="bold", color="white", zorder=5)

def draw_arrow(ax, x1, y1, x2, y2, label="", color="#7f8c8d"):
    """Dessine une flèche avec une étiquette optionnelle."""
    ax.annotate("", xy=(x2, y2), xytext=(x1, y1),
                arrowprops=dict(arrowstyle="->", color=color, lw=2))
    if label:
        mx, my = (x1 + x2) / 2, (y1 + y2) / 2
        ax.text(mx + 0.15, my + 0.05, label, fontsize=8, color=color,
                fontweight="bold", fontstyle="italic")

# Niveau 0 : question racine
draw_box(ax, 5, 5, "Contributeurs externes\nsans accès en écriture ?",
         c_question, width=3.0, height=0.8, fontsize=10)

# Branche gauche : Oui -> Forking
draw_arrow(ax, 3.5, 4.6, 1.8, 4.15, "Oui")
draw_box(ax, 1.8, 3.8, "Forking\nworkflow", c_result, width=2.0)

# Branche droite : Non -> Question suivante
draw_arrow(ax, 6.5, 4.6, 7.5, 4.15, "Non")
draw_box(ax, 7.5, 3.8, "Releases planifiées\n(v1.0, v2.0, ...) ?",
         c_question, width=2.6, height=0.8)

# Oui -> Gitflow
draw_arrow(ax, 6.2, 3.4, 4.8, 2.85, "Oui")
draw_box(ax, 4.8, 2.5, "Gitflow", c_result, width=1.8)

# Non -> Question CI/CD
draw_arrow(ax, 8.8, 3.4, 9.2, 2.85, "Non")
draw_box(ax, 8.5, 2.5, "CI/CD mature\net tests solides ?",
         c_question, width=2.6, height=0.8)

# Oui -> Trunk-based
draw_arrow(ax, 7.2, 2.1, 5.8, 1.35, "Oui")
draw_box(ax, 5.5, 1.0, "Trunk-based\ndevelopment", c_result, width=2.2)

# Non -> Feature Branch
draw_arrow(ax, 9.8, 2.1, 9.5, 1.35, "Non")
draw_box(ax, 9.0, 1.0, "Feature Branch\nworkflow", c_result, width=2.2)

plt.show()
_images/fb10bc1a18b367457fd9ae4148240bb9efc1d82065e6e8cefa504fe29e5e4c71.png

Résumé#

Le tableau suivant récapitule les caractéristiques des quatre workflows principaux.

Workflow

Branche principale

Branches supplémentaires

Revue de code

Complexité

Cas d’usage typique

Centralisé

main uniquement

Aucune

Non

Très faible

Projets personnels, prototypes

Feature Branch

main

feature/*

Pull request

Faible

Equipes de toute taille

Gitflow

main + develop

feature/*, release/*, hotfix/*

Pull request

Elevée

Releases planifiées

Trunk-based

main (tronc)

Branches éphémères (heures)

Pre-commit ou PR rapide

Moyenne

Déploiement continu

Forking

main (upstream)

Branches dans les forks

Pull request inter-forks

Moyenne

Open source

Aucun workflow n’est universellement supérieur aux autres. Le workflow centralisé convient aux projets simples ; le Feature Branch workflow offre un bon compromis entre rigueur et simplicité ; Gitflow structure les releases planifiées ; le trunk-based development accélère le déploiement continu ; et le forking workflow sécurise la collaboration open source. Le plus important n’est pas de choisir le workflow « parfait », mais d’en choisir un, de s’assurer que toute l’équipe le comprend, et de l’appliquer avec constance.