================================================================================
[OK] GIT & GITHUB EN ÉQUIPE DE 3 DÉVELOPPEURS - APPLICATION FLASK (GUIDE ULTRA-DÉTAILLÉ)
================================================================================

Ce guide couvre un cas réel et concret: une équipe de 3 développeurs Python
qui travaillent sur l'application Flask "TaskManager API" et utilisent Git
et GitHub pour versionner, collaborer, réviser et livrer leur code collectivement.

Toutes les fonctionnalités de Git et GitHub sont explorées sans exception:
branches, merges, rebases, conflits, pull requests, issues, projects, actions,
hooks, tags, releases, wikis, discussions, code review, protections de branches,
stash, cherry-pick, bisect, reflog, submodules, LFS, et bien plus encore.

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
QU'EST-CE QUE GIT ET GITHUB? POURQUOI LES UTILISER EN ÉQUIPE?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  PROBLÈME SANS GIT (situation réelle de l'équipe avant):
  ────────────────────────────────────────────────────────
  Alice  -> Envoie routes_v3_FINAL.py par email
  Bob    -> Travaille sur routes_v3_FINAL_BOB.py
  Claire -> Travaille sur routes_v3_FINAL_CLAIRE_corrige.py

  Résultat:
  - Personne ne sait quelle version est "la vraie"
  - Bob écrase le travail de Claire sans le savoir
  - Impossible de savoir qui a changé quoi et pourquoi
  - Un bug est introduit: impossible de retrouver quand et par qui
  - Déploiement = copier des fichiers à la main -> risque d'erreur

  SOLUTION AVEC GIT + GITHUB:
  ────────────────────────────
  Alice, Bob et Claire ont TOUS:
  -> Un historique complet de chaque modification (qui, quoi, quand, pourquoi)
  -> Des branches séparées pour chaque feature (aucun conflit de travail en cours)
  -> Des Pull Requests pour valider le code avant intégration
  -> Un seul dépôt partagé sur GitHub (source de vérité unique)
  -> La capacité de revenir à n'importe quel état passé en une commande

  DIFFÉRENCE GIT vs GITHUB:
  ──────────────────────────
  Git    -> Outil LOCAL installé sur chaque machine (versionnage, historique)
           Fonctionne SANS internet, SANS serveur
           Commandes: git init, git commit, git branch, git merge...

  GitHub -> SERVICE WEB (hébergement, collaboration)
           Stocke le dépôt Git en ligne
           Ajoute: Pull Requests, Issues, Actions CI/CD, Projects, Wiki...
           Alternatives: GitLab, Bitbucket, Gitea (auto-hébergé)

  CONCEPTS FONDAMENTAUX GIT:
  ───────────────────────────
  Repository (dépôt) -> Dossier versionné contenant tout l'historique
                        (.git/ contient TOUTE l'histoire du projet)

  Commit              -> Snapshot (photo) de l'état du code à un instant T
                        Identifié par un hash SHA-1 (ex: a3f8c1d)
                        Contient: auteur, date, message, changements

  Branch              -> Pointeur vers un commit (ligne de développement)
                        Créer une branche = travailler en isolation
                        main/master = branche principale

  HEAD                -> Pointeur vers le commit actuel (où vous êtes)
                        Peut pointer vers une branche ou un commit direct

  Index / Stage       -> Zone intermédiaire entre le disque et un commit
                        git add -> met dans le stage
                        git commit -> transforme le stage en commit

  Remote              -> Copie du dépôt sur un serveur (GitHub)
                        origin = nom par défaut du remote principal

  Merge               -> Fusionner deux branches en une
  Rebase              -> Réécrire l'historique en déplaçant des commits
  Tag                 -> Étiquette fixe sur un commit (ex: v1.2.0)
  Stash               -> Sauvegarde temporaire des modifications non commitées

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PRÉSENTATION DE L'ÉQUIPE ET DU PROJET
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

L'équipe:
  - Alice  -> Lead développeuse (aussi responsable Git/GitHub/DevOps)
             Email: alice@equipe.com | GitHub: @alice-dev
  - Bob    -> Développeur backend
             Email: bob@equipe.com   | GitHub: @bob-dev
  - Claire -> Développeuse backend + tests
             Email: claire@equipe.com | GitHub: @claire-dev

Le projet:
  - Nom: "TaskManager API"
  - Stack: Flask + SQLAlchemy + PostgreSQL + Redis
  - Tests: pytest + coverage
  - CI/CD: GitHub Actions
  - Registry: GitHub Container Registry
  - Hébergement dépôt: github.com/equipe/taskmanager

Structure du projet:
  taskmanager/
  ├── app/
  │   ├── __init__.py        <- Factory Flask (create_app)
  │   ├── models.py          <- Modèles SQLAlchemy (Task, User...)
  │   ├── routes.py          <- Routes/endpoints Flask
  │   ├── utils.py           <- Fonctions utilitaires
  │   └── logger.py          <- Configuration des logs
  ├── tests/
  │   ├── conftest.py        <- Fixtures pytest
  │   ├── test_routes.py     <- Tests des routes
  │   └── test_models.py     <- Tests des modèles
  ├── docker/
  │   ├── Dockerfile
  │   ├── Dockerfile.dev
  │   └── Dockerfile.test
  ├── scripts/
  │   ├── entrypoint.sh
  │   └── healthcheck.sh
  ├── requirements.txt
  ├── requirements-dev.txt
  ├── config.py
  ├── run.py
  ├── docker-compose.yml
  ├── .env.example
  ├── .gitignore
  └── README.md

Stratégie de branches (Git Flow adapté à l'équipe):
  main          -> Code de production, toujours stable, protégée
  develop       -> Intégration des features terminées
  feature/xxx   -> Développement d'une nouvelle fonctionnalité
  fix/xxx       -> Correction d'un bug
  hotfix/xxx    -> Correction urgente en production
  release/x.x.x -> Préparation d'une release

  Flux normal:
  feature/xxx -> develop -> main
                             ^
                         release tag


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 1 - CONFIGURATION INITIALE PAR ALICE (LEAD DEV)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Alice configure Git, GitHub et le dépôt pour toute l'équipe.
Bob et Claire n'ont qu'à suivre la PARTIE 2 ensuite.

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.1 - Alice installe et configure Git sur son poste
────────────────────────────────────────────────────────────────────────────────

  INSTALLATION (Mac):
  ───────────────────
  brew install git

  INSTALLATION (Ubuntu/Debian):
  ──────────────────────────────
  sudo apt-get update
  sudo apt-get install git

  INSTALLATION (Windows):
  ────────────────────────
  # Télécharger Git for Windows: https://git-scm.com/download/win
  # Ou via winget:
  winget install --id Git.Git -e --source winget

  VÉRIFICATION:
  ─────────────
  git --version
  # -> git version 2.43.0

  CONFIGURATION GLOBALE (à faire une seule fois par machine):
  ────────────────────────────────────────────────────────────
  # Identité (apparaît dans chaque commit)
  git config --global user.name "Alice Martin"
  git config --global user.email "alice@equipe.com"

  # Éditeur par défaut pour les messages de commit
  git config --global core.editor "nano"     <- ou "vim", "code --wait"

  # Stratégie de pull par défaut (rebase = historique plus propre)
  git config --global pull.rebase true

  # Branche par défaut lors de git init
  git config --global init.defaultBranch main

  # Gestion des fins de ligne (évite les conflits Windows/Linux/Mac)
  git config --global core.autocrlf input    <- Mac/Linux
  # git config --global core.autocrlf true  <- Windows

  # Activer la couleur dans le terminal
  git config --global color.ui auto

  # Alias pratiques pour l'équipe
  git config --global alias.st status
  git config --global alias.co checkout
  git config --global alias.br branch
  git config --global alias.lg "log --oneline --graph --decorate --all"
  git config --global alias.last "log -1 HEAD"
  git config --global alias.unstage "reset HEAD --"
  git config --global alias.visual "!gitk"
  git config --global alias.wip "commit -am 'WIP: work in progress'"

  # Credential helper (mémoriser le token GitHub)
  git config --global credential.helper store  <- Linux (fichier texte)
  # git config --global credential.helper osxkeychain  <- Mac
  # git config --global credential.helper manager-core  <- Windows

  VOIR SA CONFIGURATION:
  ──────────────────────
  git config --list
  git config --list --show-origin   <- Voir d'où vient chaque config
  git config user.name              <- Voir une valeur précise
  cat ~/.gitconfig                  <- Fichier de config global

  CONTENU DE ~/.gitconfig après configuration:
  ─────────────────────────────────────────────
  [user]
      name = Alice Martin
      email = alice@equipe.com
  [core]
      editor = nano
      autocrlf = input
  [pull]
      rebase = true
  [init]
      defaultBranch = main
  [color]
      ui = auto
  [alias]
      st = status
      co = checkout
      br = branch
      lg = log --oneline --graph --decorate --all
      last = log -1 HEAD
      unstage = reset HEAD --
      wip = commit -am 'WIP: work in progress'
  [credential]
      helper = store

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.2 - Alice configure l'authentification SSH avec GitHub
────────────────────────────────────────────────────────────────────────────────

SSH est plus sécurisé et pratique que HTTPS (pas besoin de saisir un mot de passe).

  GÉNÉRER UNE CLÉ SSH:
  ─────────────────────
  # Générer une paire de clés ED25519 (algorithme moderne recommandé):
  ssh-keygen -t ed25519 -C "alice@equipe.com" -f ~/.ssh/id_ed25519_github

  # L'outil demande une passphrase (mot de passe local pour la clé):
  Enter passphrase (empty for no passphrase): [alice tape sa passphrase]
  Enter same passphrase again: [confirmation]

  # Deux fichiers créés:
  # ~/.ssh/id_ed25519_github      <- Clé PRIVÉE (NE JAMAIS PARTAGER!)
  # ~/.ssh/id_ed25519_github.pub  <- Clé publique (à donner à GitHub)

  AJOUTER LA CLÉ À L'AGENT SSH:
  ───────────────────────────────
  # Démarrer l'agent SSH:
  eval "$(ssh-agent -s)"
  # -> Agent pid 12345

  # Ajouter la clé:
  ssh-add ~/.ssh/id_ed25519_github

  # Sur Mac, ajouter aussi dans le Keychain:
  # ssh-add --apple-use-keychain ~/.ssh/id_ed25519_github

  CONFIGURER ~/.ssh/config:
  ──────────────────────────
  nano ~/.ssh/config

  Contenu:
  Host github.com
      HostName github.com
      User git
      IdentityFile ~/.ssh/id_ed25519_github
      AddKeysToAgent yes

  AJOUTER LA CLÉ PUBLIQUE SUR GITHUB:
  ─────────────────────────────────────
  # Copier la clé publique:
  cat ~/.ssh/id_ed25519_github.pub
  # -> ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... alice@equipe.com

  INTERFACE WEB GitHub:
  1. Cliquer sur son avatar -> Settings
  2. SSH and GPG keys -> New SSH key
  3. Title: "MacBook Pro Alice 2024"
  4. Key type: Authentication Key
  5. Key: coller le contenu de id_ed25519_github.pub
  6. Cliquer "Add SSH key"

  TESTER LA CONNEXION:
  ─────────────────────
  ssh -T git@github.com

  SORTIE ATTENDUE:
  ────────────────
  Hi alice-dev! You've successfully authenticated,
  but GitHub does not provide shell access.

  CONFIGURER LA SIGNATURE DES COMMITS AVEC GPG (optionnel mais recommandé):
  ──────────────────────────────────────────────────────────────────────────
  # Générer une clé GPG:
  gpg --full-generate-key
  # Choisir: RSA and RSA, 4096 bits, pas d'expiration, email = alice@equipe.com

  # Lister les clés GPG:
  gpg --list-secret-keys --keyid-format=long
  # -> sec   rsa4096/ABCD1234EFGH5678

  # Configurer Git pour signer les commits:
  git config --global user.signingkey ABCD1234EFGH5678
  git config --global commit.gpgsign true
  git config --global tag.gpgsign true

  # Exporter la clé publique pour GitHub:
  gpg --armor --export ABCD1234EFGH5678

  # Sur GitHub: Settings -> SSH and GPG keys -> New GPG key -> coller la clé

  # Les commits signés apparaissent avec un badge "Verified" sur GitHub

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.3 - Alice crée le dépôt GitHub et initialise le projet
────────────────────────────────────────────────────────────────────────────────

  CRÉATION DU DÉPÔT SUR GITHUB (interface web):
  ───────────────────────────────────────────────
  1. Aller sur github.com -> bouton "+" -> "New repository"
  2. Remplir:
     - Owner: equipe (l'organisation GitHub de l'équipe)
     - Repository name: taskmanager
     - Description: "API de gestion de tâches en Flask/PostgreSQL"
     - Visibility: Private (code source propriétaire de l'équipe)
     - Initialize repository: NON (Alice va pousser un projet existant)
     - Add .gitignore: NON (Alice va créer le sien)
     - Choose a license: MIT (ou autre selon l'équipe)
  3. Cliquer "Create repository"

  GITHUB AFFICHE LES INSTRUCTIONS:
  ──────────────────────────────────
  …or create a new repository on the command line:
    git init
    git add README.md
    git commit -m "first commit"
    git branch -M main
    git remote add origin git@github.com:equipe/taskmanager.git
    git push -u origin main

  …or push an existing repository from the command line:
    git remote add origin git@github.com:equipe/taskmanager.git
    git branch -M main
    git push -u origin main

  ALICE INITIALISE LE DÉPÔT LOCAL:
  ──────────────────────────────────
  cd taskmanager/

  # Initialiser Git dans le dossier (crée .git/):
  git init

  SORTIE:
  ───────
  Initialized empty Git repository in /home/alice/taskmanager/.git/

  # Voir la structure .git/:
  ls -la .git/
  # -> branches/  config  description  HEAD  hooks/  info/  objects/  refs/

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.4 - Alice crée le fichier .gitignore
────────────────────────────────────────────────────────────────────────────────

Le .gitignore dit à Git quels fichiers ignorer (NE JAMAIS versionner).
C'est crucial: on ne doit JAMAIS committer de secrets ou de fichiers générés.

  COMMANDE:
  ─────────
  nano .gitignore

  CONTENU COMPLET ET COMMENTÉ:
  ─────────────────────────────

  # ── Secrets et configuration locale ─────────────────────────────────
  .env
  .env.*
  !.env.example          <- Exception: le template (sans secrets) est OK
  *.secret
  secrets/
  credentials/

  # ── Environnements virtuels Python ───────────────────────────────────
  venv/
  .venv/
  env/
  .env/
  ENV/
  env.bak/
  venv.bak/

  # ── Cache Python ──────────────────────────────────────────────────────
  __pycache__/
  *.py[cod]
  *$py.class
  *.pyc
  *.pyo
  *.pyd
  .Python
  build/
  develop-eggs/
  dist/
  downloads/
  eggs/
  .eggs/
  lib/
  lib64/
  parts/
  sdist/
  var/
  wheels/
  pip-wheel-metadata/
  share/python-wheels/
  *.egg-info/
  .installed.cfg
  *.egg
  MANIFEST

  # ── Tests et couverture ───────────────────────────────────────────────
  .pytest_cache/
  .coverage
  coverage.xml
  htmlcov/
  .tox/
  .nox/
  nosetests.xml
  test-results.xml
  pytest.ini.local

  # ── Base de données locale ────────────────────────────────────────────
  *.db
  *.sqlite
  *.sqlite3
  instance/

  # ── Logs ──────────────────────────────────────────────────────────────
  logs/
  *.log
  log/

  # ── Éditeurs ──────────────────────────────────────────────────────────
  .idea/
  .vscode/
  *.swp
  *.swo
  *~
  .project
  .classpath
  .settings/
  *.sublime-project
  *.sublime-workspace

  # ── OS ────────────────────────────────────────────────────────────────
  .DS_Store
  .DS_Store?
  ._*
  .Spotlight-V100
  .Trashes
  ehthumbs.db
  Thumbs.db
  desktop.ini

  # ── Docker ────────────────────────────────────────────────────────────
  .docker/
  docker-compose.override.yml    <- Config locale Docker non versionnée

  # ── SonarQube ─────────────────────────────────────────────────────────
  .scannerwork/

  # ── Jupyter ───────────────────────────────────────────────────────────
  .ipynb_checkpoints/
  *.ipynb

  # ── Mypy ──────────────────────────────────────────────────────────────
  .mypy_cache/
  .dmypy.json
  dmypy.json

  # ── Distribution / packaging ──────────────────────────────────────────
  .Python
  *.manifest
  *.spec

  VÉRIFIER QUE .gitignore FONCTIONNE:
  ────────────────────────────────────
  # Voir les fichiers ignorés:
  git check-ignore -v .env
  # -> .gitignore:3:.env    .env

  # Voir ce que Git verrait si on faisait git add:
  git status --short

  # Voir TOUS les fichiers ignorés:
  git status --ignored

  # Un fichier est déjà tracké mais devrait être ignoré?
  git rm --cached .env       <- Supprimer du tracking sans supprimer le fichier
  git rm --cached -r venv/   <- Pour un dossier entier

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.5 - Alice fait le premier commit et pousse sur GitHub
────────────────────────────────────────────────────────────────────────────────

  VOIR L'ÉTAT DU DÉPÔT:
  ─────────────────────
  git status

  SORTIE:
  ───────
  On branch main

  No commits yet

  Untracked files:
    (use "git add <file>..." to include in what will be committed)
          .dockerignore
          .env.example
          .gitignore
          README.md
          app/
          config.py
          docker/
          docker-compose.yml
          requirements.txt
          requirements-dev.txt
          run.py
          scripts/
          tests/

  nothing added to commit but untracked files present

  AJOUTER LES FICHIERS AU STAGE (INDEX):
  ────────────────────────────────────────
  # Ajouter un fichier spécifique:
  git add README.md

  # Ajouter tous les fichiers d'un dossier:
  git add app/

  # Ajouter tout sauf ce qui est dans .gitignore:
  git add .

  # Ajouter de manière interactive (choisir quoi ajouter):
  git add -p    <- Affiche chaque changement et demande: y/n/s/q...
  # y = ajouter ce chunk
  # n = ne pas ajouter
  # s = diviser en plus petits chunks
  # q = quitter
  # ? = aide

  # Voir ce qui est dans le stage:
  git diff --staged

  # Retirer un fichier du stage sans perdre les modifications:
  git restore --staged README.md
  # Ancienne syntaxe: git reset HEAD README.md

  CRÉER LE PREMIER COMMIT:
  ─────────────────────────
  git add .

  git commit -m "feat: initial project setup

  - Flask application with TaskManager API
  - SQLAlchemy models (Task)
  - REST routes (CRUD tasks + health check)
  - pytest test suite with 14 tests
  - Docker configuration (dev/test/prod)
  - docker-compose for local development
  - .env.example configuration template
  - requirements.txt with all dependencies"

  SORTIE:
  ───────
  [main (root-commit) a3f8c1d] feat: initial project setup
   23 files changed, 847 insertions(+)
   create mode 100644 .dockerignore
   create mode 100644 .env.example
   create mode 100644 .gitignore
   create mode 100644 README.md
   create mode 100644 app/__init__.py
   ...

  CONVENTION DES MESSAGES DE COMMIT (Conventional Commits):
  ───────────────────────────────────────────────────────────
  Format: <type>(<scope>): <description courte>
          [ligne vide]
          [description longue optionnelle]
          [ligne vide]
          [footer: BREAKING CHANGE, Fixes #123...]

  Types reconnus:
  feat:     Nouvelle fonctionnalité
  fix:      Correction de bug
  docs:     Documentation uniquement
  style:    Formatage, espaces (pas de changement fonctionnel)
  refactor: Refactorisation (ni feature ni bug)
  test:     Ajout ou modification de tests
  chore:    Maintenance, outils, dépendances
  perf:     Amélioration des performances
  ci:       Configuration CI/CD
  build:    Système de build, dépendances externes
  revert:   Annulation d'un commit précédent

  Exemples:
  feat(routes): add task filtering by status
  fix(models): handle null description in Task.to_dict()
  docs(readme): update installation instructions
  test(routes): add tests for task filtering
  refactor(utils): extract date formatting to helper function
  chore(deps): upgrade Flask to 3.0.0
  ci(actions): add SonarQube quality gate step

  CONNECTER LE DÉPÔT LOCAL À GITHUB ET POUSSER:
  ───────────────────────────────────────────────
  # Ajouter le remote "origin" (GitHub):
  git remote add origin git@github.com:equipe/taskmanager.git

  # Vérifier le remote:
  git remote -v
  # -> origin  git@github.com:equipe/taskmanager.git (fetch)
  # -> origin  git@github.com:equipe/taskmanager.git (push)

  # Renommer la branche locale en "main" si nécessaire:
  git branch -M main

  # Premier push avec -u (--set-upstream): lie la branche locale à la remote
  git push -u origin main

  SORTIE:
  ───────
  Enumerating objects: 28, done.
  Counting objects: 100% (28/28), done.
  Delta compression using up to 8 threads
  Compressing objects: 100% (22/22), done.
  Writing objects: 100% (28/28), 12.45 KiB | 2.49 MiB/s, done.
  Total 28 (delta 0), reused 0 (delta 0), pack-reused 0
  To git@github.com:equipe/taskmanager.git
   * [new branch]      main -> main
  Branch 'main' set up to track remote branch 'main' from 'origin'.

  VOIR L'HISTORIQUE DES COMMITS:
  ────────────────────────────────
  git log
  git log --oneline                  <- Une ligne par commit
  git log --oneline --graph          <- Avec graphique des branches
  git log --oneline --graph --all    <- Toutes les branches
  git log --author="Alice"           <- Filtrer par auteur
  git log --since="2024-01-01"       <- Depuis une date
  git log --until="2024-12-31"       <- Jusqu'à une date
  git log --grep="feat:"             <- Filtrer par message
  git log -p                         <- Avec les diffs de chaque commit
  git log --stat                     <- Avec statistiques de fichiers
  git log -- app/routes.py           <- Historique d'un fichier spécifique
  git log -S "def create_task"       <- Commits qui ont ajouté/supprimé ce texte

  SORTIE DE git log --oneline --graph --all:
  ───────────────────────────────────────────
  * a3f8c1d (HEAD -> main, origin/main) feat: initial project setup

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.6 - Alice configure les branches protégées sur GitHub
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GitHub (github.com/equipe/taskmanager):

  Settings -> Branches -> Add branch protection rule

  ─── PROTECTION DE LA BRANCHE main ──────────────────────────────────────

  Branch name pattern: main

  [x]  Require a pull request before merging
     [x]  Require approvals: 1               <- Au moins 1 approbation requise
     [x]  Dismiss stale pull request approvals when new commits are pushed
     [x]  Require review from code owners    <- Voir CODEOWNERS plus bas

  [x]  Require status checks to pass before merging
     [x]  Require branches to be up to date before merging
     Status checks required:
       + ci/tests                          <- Les tests doivent passer
       + ci/sonarqube-quality-gate         <- SonarQube doit approuver
       + ci/build-docker                   <- Le build Docker doit réussir

  [x]  Require conversation resolution before merging
     <- Tous les commentaires de review doivent être résolus

  [x]  Require signed commits
     <- Tous les commits doivent être signés avec GPG

  [x]  Include administrators
     <- Même Alice est soumise aux règles!

  [x]  Restrict who can push to matching branches
     Allowed actors: @alice-dev            <- Seule Alice peut pousser directement

  -> Save changes

  ─── PROTECTION DE LA BRANCHE develop ───────────────────────────────────

  Branch name pattern: develop

  [x]  Require a pull request before merging
     [x]  Require approvals: 1

  [x]  Require status checks to pass before merging
     Status checks required:
       + ci/tests

  -> Save changes

  CRÉER LE FICHIER CODEOWNERS:
  ─────────────────────────────
  # Ce fichier déclare qui est responsable de quelles parties du code
  # Ces personnes sont automatiquement ajoutées comme reviewers sur les PRs

  mkdir -p .github
  nano .github/CODEOWNERS

  Contenu:
  # Responsable par défaut de tout le dépôt
  *                     @equipe/tous-les-devs

  # Alice est responsable de la configuration Docker et CI/CD
  docker/               @alice-dev
  .github/              @alice-dev
  docker-compose*.yml   @alice-dev

  # Claire est responsable des tests
  tests/                @claire-dev

  # Tout le monde doit approuver les changements de config
  config.py             @alice-dev @bob-dev @claire-dev
  requirements*.txt     @alice-dev @bob-dev @claire-dev

  CRÉER LE FICHIER PULL_REQUEST_TEMPLATE.md:
  ───────────────────────────────────────────
  nano .github/PULL_REQUEST_TEMPLATE.md

  Contenu:
  ## Description
  <!-- Décrivez les changements apportés et pourquoi -->

  ## Type de changement
  - [ ] Bug fix (correction non cassante)
  - [ ] Nouvelle fonctionnalité (ajout non cassant)
  - [ ] Breaking change (modification cassante)
  - [ ] Documentation

  ## Tests
  - [ ] J'ai ajouté des tests qui prouvent que le fix/feature fonctionne
  - [ ] Les tests existants passent avec mes changements
  - [ ] J'ai mis à jour la documentation si nécessaire

  ## Captures d'écran (si applicable)

  ## Checklist
  - [ ] Mon code suit les conventions de l'équipe
  - [ ] J'ai revu mon propre code
  - [ ] Mes commits suivent la convention Conventional Commits
  - [ ] J'ai résolu tous les conflits

  ## Issues liées
  Closes #<!-- numéro de l'issue -->

  CRÉER LES TEMPLATES D'ISSUES:
  ───────────────────────────────
  mkdir -p .github/ISSUE_TEMPLATE

  # Template pour les bugs:
  nano .github/ISSUE_TEMPLATE/bug_report.md

  Contenu:
  ---
  name: Bug Report
  about: Signaler un bug
  title: '[BUG] '
  labels: bug
  assignees: ''
  ---

  ## Description du bug
  <!-- Description claire et concise -->

  ## Étapes pour reproduire
  1. Aller sur '...'
  2. Cliquer sur '...'
  3. Voir l'erreur

  ## Comportement attendu
  <!-- Ce qui devrait se passer -->

  ## Comportement actuel
  <!-- Ce qui se passe réellement -->

  ## Environnement
  - OS: [ex: macOS 14]
  - Python: [ex: 3.11]
  - Flask: [ex: 3.0.0]
  - Version: [ex: 1.2.0]

  ## Logs / Screenshots
  ```
  Coller les logs ici
  ```

  # Template pour les features:
  nano .github/ISSUE_TEMPLATE/feature_request.md

  Contenu:
  ---
  name: Feature Request
  about: Proposer une nouvelle fonctionnalité
  title: '[FEAT] '
  labels: enhancement
  assignees: ''
  ---

  ## Problème résolu
  <!-- Quel problème cette feature résout-elle? -->

  ## Solution proposée
  <!-- Description de la solution souhaitée -->

  ## Alternatives considérées
  <!-- Autres solutions envisagées -->

  ## Contexte supplémentaire
  <!-- Informations additionnelles, mockups... -->

  Alice commite et pousse ces fichiers de configuration:
  ────────────────────────────────────────────────────────
  git add .github/
  git commit -m "chore(github): add PR template, issue templates, CODEOWNERS

  - PR template with checklist for the team
  - Bug report issue template
  - Feature request issue template
  - CODEOWNERS: Alice owns Docker/CI, Claire owns tests"
  git push

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.7 - Alice crée la branche develop et la structure de branches
────────────────────────────────────────────────────────────────────────────────

  QU'EST-CE QU'UNE BRANCHE GIT?
  ───────────────────────────────
  Une branche est un pointeur léger vers un commit spécifique. Créer une
  branche ne copie PAS le code: Git crée simplement un nouveau pointeur.
  Travailler sur une branche isole les modifications: le code des autres
  branches n'est pas affecté tant qu'il n'y a pas de merge.

  Pourquoi avoir une branche "develop" en plus de "main"?
  ─────────────────────────────────────────────────────────
  main    -> Représente TOUJOURS le code en production. Jamais instable.
            Ne reçoit que des merges validés (via PR) depuis develop.

  develop -> Zone d'intégration. Les features terminées y sont mergées.
            Peut être légèrement instable (en cours d'intégration).
            Déclenche les tests CI à chaque push.

  Sans develop: chaque feature va directement dans main -> risque de
  casser la production à tout moment.

  COMMANDES:
  ──────────
  # Toujours créer develop à partir de main (même état au départ):
  git checkout main

  # Créer la branche develop à partir de main:
  git checkout -b develop

  # Ou avec la nouvelle syntaxe Git (>= 2.23):
  git switch -c develop

  SORTIE:
  ───────
  Switched to a new branch 'develop'

  # Pousser develop sur GitHub et lier la branche locale au remote:
  # -u = --set-upstream (lie develop local à origin/develop)
  # Après ce push, git pull/push saura automatiquement où pousser/tirer
  git push -u origin develop

  SORTIE:
  ───────
  Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
  To git@github.com:equipe/taskmanager.git
   * [new branch]      develop -> develop
  Branch 'develop' set up to track remote branch 'develop' from 'origin'.

  # Voir toutes les branches (locales + remote):
  git branch          <- Branches locales uniquement
  git branch -r       <- Branches remote uniquement (origin/...)
  git branch -a       <- Toutes les branches (locales + remote)
  git branch -v       <- Avec le dernier commit de chaque branche
  git branch -vv      <- Avec la branche remote liée et son statut

  SORTIE DE git branch -a:
  ─────────────────────────
  * develop
    main
    remotes/origin/develop
    remotes/origin/main

  SORTIE DE git branch -vv:
  ──────────────────────────
  * develop  a3f8c1d [origin/develop] feat: initial project setup
    main     a3f8c1d [origin/main] feat: initial project setup

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.7-B - Alice protège la branche develop sur GitHub
────────────────────────────────────────────────────────────────────────────────

  POURQUOI PROTÉGER LA BRANCHE develop?
  ───────────────────────────────────────
  Sans protection, n'importe qui peut:
  - Pousser directement sur develop (sans review, sans tests)
  - Forcer un push (git push --force) et écraser le travail des autres
  - Merger une PR sans que les tests aient passé

  Les règles de protection de branche GitHub imposent un processus:
  toute modification doit passer par une Pull Request validée.

  Cela s'applique à TOUT le monde, y compris Alice (lead dev).
  L'objectif: aucune exception, aucun raccourci, qualité garantie.

  RAPPEL COMPLET - PROTECTION DE LA BRANCHE develop:
  ─────────────────────────────────────────────────────
  (Répétition intentionnelle de l'étape 1.6 — la pédagogie par l'exemple)

  INTERFACE WEB GitHub (github.com/equipe/taskmanager):

  Settings -> Branches -> Add branch protection rule

  ─── PROTECTION DE LA BRANCHE develop ───────────────────────────────────

  Branch name pattern: develop

  Explication de chaque option:
  ──────────────────────────────

  [x]  Require a pull request before merging
     │
     │  Signification: Personne ne peut pousser directement sur develop.
     │  Toute modification doit passer par une Pull Request GitHub.
     │  Cela oblige à utiliser une branche feature, puis à ouvrir une PR.
     │  Bob et Claire ne peuvent donc jamais faire:
     │    git push origin develop  <- BLOQUÉ si cette option est active!
     │  Ils doivent faire:
     │    git push origin feature/ma-feature  <- OK
     │    [Ouvrir une PR sur GitHub]          <- OK
     │
     [x]  Require approvals: 1
     │   Signification: Au moins 1 membre de l'équipe doit approuver
     │   la PR avant qu'elle puisse être mergée. Sur develop, 1 suffit
     │   (sur main on exige 2 pour plus de sécurité). Si Bob ouvre une PR,
     │   Alice OU Claire doit l'approuver. Bob ne peut pas s'auto-approuver.
     │
     [x]  Dismiss stale pull request approvals when new commits are pushed
     │   Signification: Si Bob reçoit une approbation d'Alice, puis pousse
     │   un nouveau commit sur sa branche, l'approbation est annulée.
     │   Cela évite qu'une PR soit approuvée sur une ancienne version
     │   et mergée avec du nouveau code non relu.
     │
     [ ]  Require review from code owners
        Signification: Pour develop, on n'exige PAS le CODEOWNERS reviewer.
        C'est réservé à main pour plus de souplesse sur develop.
        (Sur main: oui, car tout changement de Docker/CI exige Alice)

  [x]  Require status checks to pass before merging
     │
     │  Signification: Avant de merger, les checks automatiques (GitHub
     │  Actions, SonarQube...) doivent tous être au vert. Si les tests
     │  échouent, le bouton "Merge" est grisé sur GitHub.
     │
     [x]  Require branches to be up to date before merging
     │   Signification: La branche feature doit être à jour avec develop
     │   avant de merger. Cela évite de merger du code qui n'a pas été
     │   testé avec les derniers commits de develop.
     │   Bob doit donc faire: git rebase origin/develop avant sa PR.
     │
     Status checks required:
       + ci/tests
         <- Le job "tests" de GitHub Actions doit être vert (pytest passe)
           Chemin exact: .github/workflows/ci.yml -> job "tests"

  [x]  Require conversation resolution before merging
     │
     │  Signification: Tous les commentaires ouverts dans la PR doivent
     │  être marqués comme résolus avant de permettre le merge.
     │  Cela empêche de merger une PR dont certains commentaires
     │  (ex: "ce code a un bug") n'ont pas été traités.

  [ ]  Require signed commits
     │
     │  Signification: Pour develop on n'exige PAS la signature GPG.
     │  C'est réservé à main (production). Exiger GPG sur develop
     │  serait trop contraignant au quotidien pour Bob et Claire.

  [x]  Include administrators
     │
     │  Signification: Ces règles s'appliquent AUSSI aux administrateurs
     │  du dépôt (dont Alice). Sans cette option, Alice pourrait contourner
     │  les règles. Avec cette option, personne n'est au-dessus des règles.

  [x]  Do not allow bypassing the above settings
     │
     │  Signification: Renforce l'option précédente. Même les personnes
     │  avec le rôle "admin" ne peuvent pas bypasser ces règles.
     │  C'est la garantie ultime que le processus est respecté.

  [ ]  Restrict who can push to matching branches
     │
     │  Signification: Pour develop, on ne restreint PAS qui peut pousser
     │  (via PR). Tout membre de l'équipe peut ouvrir une PR vers develop.
     │  C'est différent de main où seule Alice peut merger.

  [ ]  Allow force pushes
     │
     │  Signification: DÉSACTIVÉ. Personne ne peut faire git push --force
     │  sur develop. Cela protège l'historique partagé de la branche.
     │  Un force push peut écraser les commits d'autres développeurs.

  [ ]  Allow deletions
     │
     │  Signification: DÉSACTIVÉ. Personne ne peut supprimer la branche
     │  develop (ni accidentellement, ni intentionnellement).

  -> Save changes

  RÉSUMÉ VISUEL des deux niveaux de protection:
  ───────────────────────────────────────────────

  Règle                              main    develop
  ─────────────────────────────────  ──────  ──────────
  PR obligatoire avant merge         [OK] Oui   [OK] Oui
  Nombre d'approbations requises     [OK] 2     [OK] 1
  Review CODEOWNERS requise          [OK] Oui   [X] Non
  Tests CI obligatoires              [OK] Oui   [OK] Oui
  SonarQube Quality Gate             [OK] Oui   [X] Non
  Build Docker                       [OK] Oui   [X] Non
  Branche à jour avant merge         [OK] Oui   [OK] Oui
  Conversations résolues             [OK] Oui   [OK] Oui
  Commits signés GPG                 [OK] Oui   [X] Non
  Admins soumis aux règles           [OK] Oui   [OK] Oui
  Force push interdit                [OK] Oui   [OK] Oui
  Suppression interdite              [OK] Oui   [OK] Oui
  Restriction des pusheurs           [OK] Alice  [X] Tous

  Pourquoi main est plus stricte que develop?
  -> main = production = impact direct sur les utilisateurs.
    Une erreur en production peut causer une panne pour tous les clients.
    develop = intégration = vu seulement par l'équipe.
    Une erreur sur develop est détectée avant d'atteindre les utilisateurs.

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.7-C - Alice crée des labels personnalisés sur GitHub
────────────────────────────────────────────────────────────────────────────────

  QU'EST-CE QU'UN LABEL GITHUB?
  ───────────────────────────────
  Un label (étiquette) est une catégorie colorée qu'on attache à une Issue
  ou une Pull Request pour la classifier. Les labels permettent de:

  - Filtrer rapidement les issues: "montre-moi tous les bugs de priorité haute"
  - Trier le backlog: "quelles issues sont bloquées?"
  - Automatiser: GitHub Actions peut déclencher des actions selon les labels
  - Communiquer: un label "help wanted" signale qu'on cherche de l'aide
  - Suivre l'état: "in progress" montre qu'une issue est en cours

  Un dépôt GitHub propose des labels par défaut (bug, enhancement, etc.)
  mais l'équipe peut créer des labels personnalisés adaptés à son projet.

  STRATÉGIE DE LABELS DE L'ÉQUIPE:
  ──────────────────────────────────
  Alice organise les labels en plusieurs catégories logiques:
  - Type de travail     -> bug, enhancement, documentation
  - Priorité            -> priority: high/medium/low
  - État                -> in progress, blocked, wontfix
  - Technique           -> flask, database, tests, ci/cd, docker
  - Méta                -> good first issue, help wanted, duplicate

  Cette organisation permet des recherches combinées:
    label:bug + label:"priority: high"       -> bugs urgents à corriger
    label:enhancement + label:"in progress"  -> features en cours
    label:blocked                            -> issues nécessitant attention

  COMMENT CRÉER UN LABEL SUR GITHUB (pas à pas):
  ─────────────────────────────────────────────────
  1. Aller sur github.com/equipe/taskmanager
  2. Cliquer sur l'onglet "Issues"
  3. Cliquer sur "Labels" (à droite de la barre de recherche)
  4. Cliquer "New label"
  5. Remplir:
     - Label name: "priority: high"
     - Description: "Priorité haute, à traiter en premier"
     - Color: cliquer le carré de couleur -> entrer #e11d48 ou générer aléatoire
  6. Cliquer "Create label"
  7. Répéter pour chaque label de la liste ci-dessous

  COMMENT MODIFIER UN LABEL EXISTANT:
  ─────────────────────────────────────
  1. Issues -> Labels -> cliquer l'icône crayon ([EDIT]) à droite du label
  2. Modifier nom/couleur/description -> "Save changes"

  COMMENT SUPPRIMER UN LABEL:
  ────────────────────────────
  1. Issues -> Labels -> cliquer "Delete" à droite du label
  2. Attention: supprimer un label le retire de toutes les issues!

  VIA GITHUB CLI (alternative sans interface web):
  ─────────────────────────────────────────────────
  gh label create "priority: high" --color "#e11d48" --description "Priorité haute"
  gh label create "priority: medium" --color "#f97316" --description "Priorité moyenne"
  gh label create "priority: low" --color "#86efac" --description "Priorité basse"
  gh label list    <- Voir tous les labels

  LISTE COMPLÈTE DES LABELS CRÉÉS PAR ALICE:
  ────────────────────────────────────────────
  INTERFACE WEB: Issues -> Labels -> New label

  ── CATÉGORIE: TYPE DE TRAVAIL ──────────────────────────────────────────
  ┌──────────────────┬──────────┬──────────────────────────────────────────┐
  │ Nom              │ Couleur  │ Description et utilisation               │
  ├──────────────────┼──────────┼──────────────────────────────────────────┤
  │ bug              │ #d73a4a  │ Quelque chose ne fonctionne pas.         │
  │                  │ (rouge)  │ Utilisé pour les issues signalant un     │
  │                  │          │ comportement incorrect de l'application. │
  │                  │          │ Ex: "POST /tasks retourne 500"           │
  ├──────────────────┼──────────┼──────────────────────────────────────────┤
  │ enhancement      │ #a2eeef  │ Nouvelle fonctionnalité ou amélioration. │
  │                  │ (cyan)   │ Utilisé pour les demandes de features.   │
  │                  │          │ Ex: "Ajouter la pagination à GET /tasks" │
  ├──────────────────┼──────────┼──────────────────────────────────────────┤
  │ documentation    │ #0075ca  │ Concerne uniquement la documentation.    │
  │                  │ (bleu)   │ Ex: "Mettre à jour README.md"            │
  │                  │          │ Ex: "Ajouter docstrings à routes.py"     │
  ├──────────────────┼──────────┼──────────────────────────────────────────┤
  │ breaking change  │ #7c3aed  │ Modification incompatible avec           │
  │                  │ (violet) │ les versions précédentes.                │
  │                  │          │ Toujours mentionner dans le CHANGELOG.   │
  │                  │          │ Ex: "Changer le format de réponse API"   │
  └──────────────────┴──────────┴──────────────────────────────────────────┘

  ── CATÉGORIE: PRIORITÉ ─────────────────────────────────────────────────
  ┌──────────────────┬──────────┬──────────────────────────────────────────┐
  │ Nom              │ Couleur  │ Description et utilisation               │
  ├──────────────────┼──────────┼──────────────────────────────────────────┤
  │ priority: high   │ #e11d48  │ Priorité haute. À traiter en premier,    │
  │                  │ (rouge   │ avant toute autre issue. Bloque souvent  │
  │                  │  vif)    │ d'autres développements.                 │
  │                  │          │ Ex: bug critique en production           │
  ├──────────────────┼──────────┼──────────────────────────────────────────┤
  │ priority: medium │ #f97316  │ Priorité normale. À traiter dans le      │
  │                  │ (orange) │ sprint en cours ou le suivant.           │
  │                  │          │ Ex: amélioration de performance          │
  ├──────────────────┼──────────┼──────────────────────────────────────────┤
  │ priority: low    │ #86efac  │ Priorité basse. Nice-to-have.            │
  │                  │ (vert    │ À traiter quand l'équipe a du temps libre│
  │                  │  clair)  │ Ex: amélioration cosmétique des logs     │
  └──────────────────┴──────────┴──────────────────────────────────────────┘

  ── CATÉGORIE: ÉTAT ─────────────────────────────────────────────────────
  ┌──────────────────┬──────────┬──────────────────────────────────────────┐
  │ Nom              │ Couleur  │ Description et utilisation               │
  ├──────────────────┼──────────┼──────────────────────────────────────────┤
  │ in progress      │ #fde68a  │ Quelqu'un travaille activement sur       │
  │                  │ (jaune)  │ cette issue en ce moment.                │
  │                  │          │ -> Évite que deux devs fassent la même    │
  │                  │          │   chose en parallèle sans le savoir.     │
  ├──────────────────┼──────────┼──────────────────────────────────────────┤
  │ blocked          │ #6b7280  │ Cette issue ne peut pas avancer car elle │
  │                  │ (gris)   │ dépend d'autre chose (autre issue, décision│
  │                  │          │ externe, dépendance technique).          │
  │                  │          │ Toujours expliquer le blocage en comment.│
  ├──────────────────┼──────────┼──────────────────────────────────────────┤
  │ wontfix          │ #ffffff  │ Cette issue ne sera pas corrigée/        │
  │                  │ (blanc)  │ implémentée. Expliquer pourquoi en       │
  │                  │          │ commentaire avant de fermer l'issue.     │
  │                  │          │ Ex: hors scope, doublon inutile          │
  ├──────────────────┼──────────┼──────────────────────────────────────────┤
  │ duplicate        │ #cfd3d7  │ Cette issue existe déjà (doublon).       │
  │                  │ (gris    │ Toujours pointer vers l'issue originale  │
  │                  │  clair)  │ avant de fermer: "Doublon de #12"        │
  └──────────────────┴──────────┴──────────────────────────────────────────┘

  ── CATÉGORIE: COMMUNAUTÉ ───────────────────────────────────────────────
  ┌──────────────────┬──────────┬──────────────────────────────────────────┐
  │ Nom              │ Couleur  │ Description et utilisation               │
  ├──────────────────┼──────────┼──────────────────────────────────────────┤
  │ good first issue │ #7057ff  │ Bonne issue pour un nouveau contributeur │
  │                  │ (violet  │ ou un dev junior. Périmètre clair,       │
  │                  │  clair)  │ pas de complexité cachée.                │
  │                  │          │ -> GitHub l'affiche dans "Explore"        │
  ├──────────────────┼──────────┼──────────────────────────────────────────┤
  │ help wanted      │ #008672  │ L'équipe cherche de l'aide ou un avis    │
  │                  │ (vert    │ extérieur sur cette issue. Peut être      │
  │                  │  foncé)  │ combiné avec "good first issue".         │
  └──────────────────┴──────────┴──────────────────────────────────────────┘

  ── CATÉGORIE: TECHNIQUE (spécifique au projet Flask) ───────────────────
  ┌──────────────────┬──────────┬──────────────────────────────────────────┐
  │ Nom              │ Couleur  │ Description et utilisation               │
  ├──────────────────┼──────────┼──────────────────────────────────────────┤
  │ flask            │ #059669  │ Concerne les routes Flask, les blueprints│
  │                  │ (vert    │ ou le framework web lui-même.            │
  │                  │  émeraude│ Ex: "Ajouter un middleware d'auth"       │
  ├──────────────────┼──────────┼──────────────────────────────────────────┤
  │ database         │ #0284c7  │ Concerne PostgreSQL, SQLAlchemy,         │
  │                  │ (bleu    │ les modèles ou les migrations.           │
  │                  │  ciel)   │ Ex: "Ajouter index sur Task.created_at"  │
  ├──────────────────┼──────────┼──────────────────────────────────────────┤
  │ tests            │ #7c3aed  │ Concerne pytest, la couverture de code,  │
  │                  │ (violet) │ les fixtures ou la stratégie de tests.   │
  │                  │          │ Ex: "Coverage de utils.py est à 72%"     │
  ├──────────────────┼──────────┼──────────────────────────────────────────┤
  │ ci/cd            │ #6366f1  │ Concerne GitHub Actions, les pipelines,  │
  │                  │ (indigo) │ le déploiement automatique.              │
  │                  │          │ Ex: "Ajouter job de sécurité Bandit"     │
  ├──────────────────┼──────────┼──────────────────────────────────────────┤
  │ docker           │ #0369a1  │ Concerne les Dockerfiles, docker-compose,│
  │                  │ (bleu    │ les images ou le registry.               │
  │                  │  foncé)  │ Ex: "Réduire la taille de l'image prod"  │
  └──────────────────┴──────────┴──────────────────────────────────────────┘

  COMMENT UTILISER LES LABELS EN PRATIQUE:
  ──────────────────────────────────────────
  # Lors de la création d'une issue:
  -> Toujours ajouter AU MOINS un label "type" (bug, enhancement...)
  -> Ajouter un label "priority" si connu
  -> Ajouter un label "technique" si applicable

  # Exemple de combinaisons courantes:
  bug + priority: high + flask        -> Bug urgent dans les routes
  enhancement + priority: medium + database -> Feature DB à planifier
  documentation + priority: low       -> Doc à améliorer quand possible
  bug + blocked                       -> Bug connu, bloqué en attente de...
  enhancement + good first issue      -> Feature simple pour Bob ou Claire

  # Filtrer les issues par label:
  INTERFACE WEB: Issues -> cliquer "Labels" -> choisir un label
  OU dans la barre de recherche: label:bug label:"priority: high"
  OU avec gh CLI: gh issue list --label "bug" --label "priority: high"

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.7-D - Alice crée les Milestones (Jalons) sur GitHub
────────────────────────────────────────────────────────────────────────────────

  QU'EST-CE QU'UN MILESTONE (JALON)?
  ─────────────────────────────────────
  Un Milestone est un objectif daté qui regroupe plusieurs Issues et Pull
  Requests. Il représente une version, une livraison ou une étape du projet.

  Concrètement, un Milestone c'est:
  - Un nom:        "v1.0.0 - MVP"
  - Une date:      2024-02-01
  - Une description: "Fonctionnalités de base pour la première livraison"
  - Des issues:    Toutes les issues nécessaires pour atteindre cet objectif

  GitHub calcule automatiquement la progression du Milestone:
  "8 of 12 issues closed" -> 66% d'avancement

  Pourquoi utiliser les Milestones plutôt que de simples labels?
  ──────────────────────────────────────────────────────────────
  Labels  -> Classification transversale (type, priorité, état)
  Milestones -> Regroupement temporel orienté livraison

  Un bug peut avoir le label "priority: high" ET appartenir au milestone
  "v1.0.1" -> il est urgent ET bloque la prochaine release de patch.

  L'avancement d'un Milestone donne une réponse à: "Quand sera-t-on prêts
  à livrer la v1.1.0?" -> "14 issues restantes sur 20, deadline: 1er mars"

  COMMENT CRÉER UN MILESTONE SUR GITHUB (pas à pas):
  ────────────────────────────────────────────────────
  1. Aller sur github.com/equipe/taskmanager
  2. Cliquer sur l'onglet "Issues"
  3. Cliquer sur "Milestones" (à droite de la barre de recherche)
  4. Cliquer "New milestone"
  5. Remplir:
     - Title:       "v1.0.0 - MVP"
     - Due date:    2024-02-01
     - Description: "Première version stable avec CRUD complet et filtrage"
  6. Cliquer "Create milestone"
  7. Répéter pour chaque milestone ci-dessous

  COMMENT ASSOCIER UNE ISSUE À UN MILESTONE:
  ────────────────────────────────────────────
  Méthode 1 - Lors de la création d'une issue:
    -> Panneau latéral droit "Milestone" -> sélectionner le milestone

  Méthode 2 - Sur une issue existante:
    -> Ouvrir l'issue -> panneau "Milestone" -> Edit -> sélectionner

  Méthode 3 - En masse depuis la liste des issues:
    -> Issues -> cocher plusieurs issues -> "Milestone" -> choisir le milestone

  VIA GITHUB CLI:
  ────────────────
  gh api repos/equipe/taskmanager/milestones \
    --method POST \
    --field title="v1.0.0 - MVP" \
    --field due_on="2024-02-01T00:00:00Z" \
    --field description="Fonctionnalités de base pour la première livraison"

  MILESTONES CRÉÉS PAR ALICE POUR LE PROJET:
  ────────────────────────────────────────────
  INTERFACE WEB: Issues -> Milestones -> New milestone

  ┌─────────────────────────────────────────────────────────────────────┐
  │  MILESTONE: v1.0.0 - MVP                                           │
  │  Due date:  2024-02-01                                             │
  │  Description:                                                       │
  │  "Première version stable avec les fonctionnalités de base:        │
  │   CRUD tâches, filtrage, pagination, health check.                 │
  │   Prête pour un déploiement en production initial."                │
  │                                                                     │
  │  Issues associées:                                                  │
  │  #1 - CRUD complet (GET/POST/PUT/DELETE /tasks)    [CLOSED [OK]]    │
  │  #2 - Endpoint /health                             [CLOSED [OK]]    │
  │  #3 - Logging structuré JSON                       [CLOSED [OK]]    │
  │  #5 - Filtrage des tâches (statut, dates, search)  [CLOSED [OK]]    │
  │  #6 - Filtre par priorité                          [CLOSED [OK]]    │
  │  #7 - Filtre par tags                              [OPEN   [BLEU]]    │
  │  #8 - Fix type description (BUG)                   [CLOSED [OK]]    │
  │                                                                     │
  │  Progression: 6 of 7 issues closed -> 86% ████████░ 86%           │
  └─────────────────────────────────────────────────────────────────────┘

  ┌─────────────────────────────────────────────────────────────────────┐
  │  MILESTONE: v1.1.0 - Authentication                                │
  │  Due date:  2024-03-01                                             │
  │  Description:                                                       │
  │  "Système d'authentification JWT pour sécuriser l'API.            │
  │   Les utilisateurs pourront s'inscrire, se connecter              │
  │   et gérer leurs propres tâches."                                  │
  │                                                                     │
  │  Issues associées:                                                  │
  │  #9  - Modèle User + endpoints /auth/register, /auth/login        │
  │  #10 - Middleware JWT d'authentification                           │
  │  #11 - Endpoint /auth/me (profil utilisateur)                     │
  │  #12 - Associer les tâches à un utilisateur (Task.user_id)        │
  │  #13 - Tests d'authentification                                    │
  │                                                                     │
  │  Progression: 0 of 5 issues closed -> 0% ░░░░░░░░░ 0%            │
  └─────────────────────────────────────────────────────────────────────┘

  ┌─────────────────────────────────────────────────────────────────────┐
  │  MILESTONE: v1.2.0 - Performance & Cache                           │
  │  Due date:  2024-04-01                                             │
  │  Description:                                                       │
  │  "Intégration du cache Redis pour améliorer les performances.     │
  │   Objectif: réduire le temps de réponse de GET /tasks de 50%."   │
  │                                                                     │
  │  Issues associées:                                                  │
  │  #14 - Cache Redis pour GET /tasks (Flask-Caching)                │
  │  #15 - Rate limiting par IP (Flask-Limiter)                       │
  │  #16 - Index PostgreSQL sur Task.created_at et Task.done          │
  │  #17 - Benchmarks de performance (avant/après cache)              │
  │                                                                     │
  │  Progression: 0 of 4 issues closed -> 0% ░░░░░░░░░ 0%            │
  └─────────────────────────────────────────────────────────────────────┘

  ┌─────────────────────────────────────────────────────────────────────┐
  │  MILESTONE: v2.0.0 - Refactoring majeur                            │
  │  Due date:  2024-06-01                                             │
  │  Description:                                                       │
  │  "Refactorisation profonde de l'architecture:                     │
  │   migration vers une architecture en couches (services, repos).   │
  │   BREAKING CHANGES prévus sur l'API."                             │
  │                                                                     │
  │  Issues associées: (à définir lors du sprint planning)            │
  │                                                                     │
  │  Progression: 0 of 0 issues closed -> N/A                         │
  └─────────────────────────────────────────────────────────────────────┘

  COMMENT CONSULTER L'AVANCEMENT D'UN MILESTONE:
  ────────────────────────────────────────────────
  INTERFACE WEB: Issues -> Milestones
  -> Vue liste de tous les milestones avec barres de progression

  INTERFACE WEB: Issues -> Milestones -> cliquer sur "v1.0.0 - MVP"
  -> Vue détaillée: toutes les issues du milestone, filtrables par état

  VIA GITHUB CLI:
  ────────────────
  gh api repos/equipe/taskmanager/milestones   <- Lister tous les milestones
  gh issue list --milestone "v1.0.0 - MVP"     <- Issues d'un milestone

  FERMER UN MILESTONE (après livraison de la version):
  ─────────────────────────────────────────────────────
  INTERFACE WEB: Issues -> Milestones -> "v1.0.0 - MVP" -> "Close milestone"
  -> Le milestone est archivé mais reste consultable
  -> Les issues non fermées sont "orphelines" (à déplacer ou fermer manuellement)

  VIA GITHUB CLI:
  gh api repos/equipe/taskmanager/milestones/1 \
    --method PATCH --field state="closed"


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 2 - CONFIGURATION POSTE DE BOB ET CLAIRE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 2.1 - Bob configure Git et clone le dépôt (Ubuntu)
────────────────────────────────────────────────────────────────────────────────

BOB dans son terminal:

  # Installer Git:
  sudo apt-get install git

  # Configuration globale (identique au processus d'Alice):
  git config --global user.name "Bob Dupont"
  git config --global user.email "bob@equipe.com"
  git config --global core.editor "nano"
  git config --global pull.rebase true
  git config --global init.defaultBranch main
  git config --global core.autocrlf input
  git config --global color.ui auto
  git config --global alias.st status
  git config --global alias.co checkout
  git config --global alias.br branch
  git config --global alias.lg "log --oneline --graph --decorate --all"

  # Générer et ajouter sa clé SSH à GitHub (même processus qu'Alice):
  ssh-keygen -t ed25519 -C "bob@equipe.com" -f ~/.ssh/id_ed25519_github
  eval "$(ssh-agent -s)"
  ssh-add ~/.ssh/id_ed25519_github
  cat ~/.ssh/id_ed25519_github.pub   <- Copier et ajouter sur GitHub

  # Cloner le dépôt (télécharge tout l'historique et le code):
  git clone git@github.com:equipe/taskmanager.git
  cd taskmanager/

  SORTIE:
  ───────
  Cloning into 'taskmanager'...
  remote: Enumerating objects: 28, done.
  remote: Counting objects: 100% (28/28), done.
  remote: Compressing objects: 100% (22/22), done.
  Receiving objects: 100% (28/28), 12.45 KiB | 3.11 MiB/s, done.

  # Voir les branches disponibles:
  git branch -a

  SORTIE:
  ───────
  * main
    remotes/origin/HEAD -> origin/main
    remotes/origin/develop
    remotes/origin/main

  # Configurer son .env local:
  cp .env.example .env
  nano .env

  # Créer la branche develop localement (suivi de la remote):
  git checkout develop
  # Ou: git switch develop

  SORTIE:
  ───────
  Branch 'develop' set up to track remote branch 'develop' from 'origin'.
  Switched to a new branch 'develop'

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 2.2 - Claire configure Git et clone le dépôt (Windows)
────────────────────────────────────────────────────────────────────────────────

CLAIRE sur Windows (Git Bash ou PowerShell):

  # Installer Git for Windows (inclut Git Bash)
  # https://git-scm.com/download/win

  # Dans Git Bash:
  git config --global user.name "Claire Leblanc"
  git config --global user.email "claire@equipe.com"
  git config --global core.editor "nano"
  git config --global pull.rebase true
  git config --global core.autocrlf true   <- Windows: CRLF en local, LF sur remote
  git config --global color.ui auto

  # Note sur les fins de ligne:
  # Windows utilise CRLF (\r\n), Linux/Mac utilisent LF (\n)
  # core.autocrlf=true sur Windows convertit automatiquement
  # Cela évite les conflits dus aux fins de ligne dans les PRs

  # Générer clé SSH pour Claire:
  ssh-keygen -t ed25519 -C "claire@equipe.com"
  # Sous Windows: clé dans C:\Users\claire\.ssh\

  # Cloner le dépôt:
  git clone git@github.com:equipe/taskmanager.git
  cd taskmanager

  # Setup développement:
  cp .env.example .env
  git checkout develop


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 3 - WORKFLOW QUOTIDIEN AVEC LES BRANCHES
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

════════════════════════════════════════════════════════════════════════════════
SCÉNARIO A - Bob développe une nouvelle feature: filtrage des tâches
════════════════════════════════════════════════════════════════════════════════

──────────────────────────────────────────────────────────────────────────────
A.1 - Bob crée une issue GitHub pour la feature
──────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GitHub: Issues -> New issue -> Feature Request template

  Title: [FEAT] Add task filtering by status and date range
  Body:
    ## Problème résolu
    Actuellement, GET /tasks retourne toutes les tâches sans filtre possible.
    Les clients de l'API ont besoin de filtrer par statut (done/not done)
    et par plage de dates (created_at).

    ## Solution proposée
    Ajouter des query parameters à GET /tasks:
    - ?done=true/false -> filtrer par statut
    - ?from=2024-01-01 -> date de début
    - ?to=2024-01-31   -> date de fin
    - ?search=keyword  -> recherche textuelle dans le titre

    ## Exemples d'utilisation
    GET /tasks?done=false           -> tâches non terminées
    GET /tasks?from=2024-01-01&to=2024-01-31  -> tâches de janvier
    GET /tasks?search=urgent&done=false -> tâches urgentes non terminées

  Labels: enhancement, flask, priority: medium
  Milestone: v1.0.0
  Assignees: @bob-dev

  -> Submit new issue -> Issue #5 créée

──────────────────────────────────────────────────────────────────────────────
A.2 - Bob crée sa branche feature depuis develop
──────────────────────────────────────────────────────────────────────────────

BOB dans son terminal:

  # Toujours partir d'un develop à jour:
  git checkout develop
  git pull origin develop

  # Créer la branche feature (convention: feature/NUMÉRO-description):
  git checkout -b feature/5-task-filtering

  SORTIE:
  ───────
  Switched to a new branch 'feature/5-task-filtering'

  # Vérifier qu'on est sur la bonne branche:
  git branch
  # -> * feature/5-task-filtering
  #     develop
  #     main

  # Lier la branche locale au remote dès le début:
  git push -u origin feature/5-task-filtering

  SORTIE:
  ───────
  Total 0 (delta 0), reused 0 (delta 0)
  To git@github.com:equipe/taskmanager.git
   * [new branch]      feature/5-task-filtering -> feature/5-task-filtering

──────────────────────────────────────────────────────────────────────────────
A.3 - Bob développe la feature avec des commits atomiques
──────────────────────────────────────────────────────────────────────────────

Bob modifie app/routes.py pour ajouter le filtrage.

  MODIFIER app/routes.py:
  ───────────────────────

  @tasks_bp.route('/tasks', methods=['GET'])
  def get_tasks():
      """
      Récupérer les tâches avec filtres optionnels.
      Query params: done, from, to, search, page, per_page
      """
      query = Task.query

      # Filtre par statut done/not done
      done_param = request.args.get('done')
      if done_param is not None:
          done = done_param.lower() == 'true'
          query = query.filter(Task.done == done)

      # Filtre par plage de dates
      from_date = request.args.get('from')
      if from_date:
          try:
              from_dt = datetime.strptime(from_date, '%Y-%m-%d')
              query = query.filter(Task.created_at >= from_dt)
          except ValueError:
              return jsonify({"error": "Invalid 'from' date format. Use YYYY-MM-DD"}), 400

      to_date = request.args.get('to')
      if to_date:
          try:
              to_dt = datetime.strptime(to_date, '%Y-%m-%d')
              query = query.filter(Task.created_at <= to_dt)
          except ValueError:
              return jsonify({"error": "Invalid 'to' date format. Use YYYY-MM-DD"}), 400

      # Recherche textuelle
      search = request.args.get('search')
      if search:
          query = query.filter(Task.title.ilike(f'%{search}%'))

      # Pagination
      page = request.args.get('page', 1, type=int)
      per_page = request.args.get('per_page', 20, type=int)
      tasks = query.paginate(page=page, per_page=per_page, error_out=False)

      return jsonify({
          "tasks": [t.to_dict() for t in tasks.items],
          "total": tasks.total,
          "page": tasks.page,
          "per_page": tasks.per_page,
          "pages": tasks.pages
      })

  COMMIT ATOMIQUE 1 (logique métier seulement):
  ───────────────────────────────────────────────
  git add app/routes.py
  git commit -m "feat(routes): add task filtering by status, date range and search

  - Filter by done status: ?done=true/false
  - Filter by date range: ?from=YYYY-MM-DD&to=YYYY-MM-DD
  - Search in title: ?search=keyword
  - Add pagination: ?page=N&per_page=N

  Implements #5"

  Bob modifie maintenant tests/test_routes.py pour ajouter les tests.

  VOIR CE QUI A CHANGÉ DEPUIS LE DERNIER COMMIT:
  ────────────────────────────────────────────────
  git diff                   <- Modifications non stagées
  git diff --staged          <- Modifications stagées (prêtes pour commit)
  git diff HEAD              <- Toutes les modifications (staged + unstaged)
  git diff main..develop     <- Différence entre deux branches
  git diff a3f8c1d..b7d2e4f  <- Différence entre deux commits

  COMMIT ATOMIQUE 2 (tests):
  ───────────────────────────
  git add tests/test_routes.py
  git commit -m "test(routes): add tests for task filtering

  - Test filter by done=true/false
  - Test filter by date range
  - Test search by keyword
  - Test pagination
  - Test invalid date format returns 400

  Coverage: routes.py 96% (+2%)"

──────────────────────────────────────────────────────────────────────────────
A.4 - Bob utilise git stash quand il doit s'interrompre
──────────────────────────────────────────────────────────────────────────────

Pendant que Bob développe, Alice lui signale un bug urgent sur develop.
Bob doit s'interrompre mais n'a pas terminé sa feature.

BOB dans son terminal:

  # Bob a des modifications en cours non commitées:
  git status
  # -> modified: app/routes.py (travail en cours non terminé)

  # Il ne veut pas faire un commit "sale" -> il utilise stash:
  git stash save "WIP: ajout de tri par priorité au filtrage"

  SORTIE:
  ───────
  Saved working directory and index state On feature/5-task-filtering:
  WIP: ajout de tri par priorité au filtrage

  # Vérifier que le working directory est propre:
  git status
  # -> nothing to commit, working tree clean

  # Voir les stashs existants:
  git stash list
  # -> stash@{0}: On feature/5-task-filtering: WIP: ajout de tri par priorité

  # Bob passe sur develop pour corriger le bug:
  git checkout develop
  git pull origin develop
  # [Bob corrige le bug, commit, push...]

  # Bob revient sur sa feature et restaure son travail:
  git checkout feature/5-task-filtering

  # Appliquer le stash (et le supprimer de la liste):
  git stash pop

  # Ou appliquer sans supprimer (peut être réappliqué):
  git stash apply stash@{0}

  # Supprimer un stash spécifique:
  git stash drop stash@{0}

  # Supprimer tous les stashs:
  git stash clear

  # Appliquer le stash dans une nouvelle branche:
  git stash branch nouvelle-branche stash@{0}

  # Voir le contenu d'un stash sans l'appliquer:
  git stash show -p stash@{0}

──────────────────────────────────────────────────────────────────────────────
A.5 - Bob met à jour sa branche avec les derniers changements de develop
──────────────────────────────────────────────────────────────────────────────

Après quelques jours, develop a reçu des commits d'Alice et Claire.
Bob doit intégrer ces changements dans sa branche.

  MÉTHODE 1: REBASE (recommandée pour les features locales)
  ──────────────────────────────────────────────────────────
  # Récupérer les derniers changements sans merger:
  git fetch origin

  # Rebaser sa branche sur le develop à jour:
  git rebase origin/develop

  SCHÉMA AVANT rebase:
  ─────────────────────
  main:    A──B
               \
  develop: ──C──D──E  (E et F sont d'Alice et Claire)
               \
  feature:  ──X──Y  (X et Y sont les commits de Bob)

  SCHÉMA APRÈS git rebase origin/develop:
  ─────────────────────────────────────────
  main:    A──B
               \
  develop: ──C──D──E
                    \
  feature:       ──X'──Y'  (X et Y "réécrits" sur E)

  # L'historique est linéaire et propre!

  MÉTHODE 2: MERGE (conserve l'historique exact)
  ────────────────────────────────────────────────
  git fetch origin
  git merge origin/develop

  SCHÉMA APRÈS git merge origin/develop:
  ───────────────────────────────────────
  main:    A──B
               \
  develop: ──C──D──E
               \    \
  feature:  ──X──Y──M  (M = merge commit)

  # L'historique montre exactement ce qui s'est passé
  # Mais il est moins linéaire

  RÈGLE DE L'ÉQUIPE:
  ───────────────────
  - Rebase: pour les branches feature locales, avant une PR
  - Merge: pour intégrer develop -> main (via PR)
  - JAMAIS rebase une branche partagée avec d'autres (dangereux!)

──────────────────────────────────────────────────────────────────────────────
A.6 - Bob ouvre une Pull Request sur GitHub
──────────────────────────────────────────────────────────────────────────────

Bob pousse sa branche finale et ouvre une PR.

BOB dans son terminal:

  # Pousser la branche à jour:
  git push origin feature/5-task-filtering

  # Si rebase a réécrit l'historique -> push forcé nécessaire:
  git push --force-with-lease origin feature/5-task-filtering
  # --force-with-lease est plus sûr que --force:
  # Il vérifie que personne d'autre n'a poussé entre temps

INTERFACE WEB GitHub:

  GitHub affiche: "feature/5-task-filtering had recent pushes"
  -> Cliquer "Compare & pull request"

  Remplir le formulaire de PR:
  ────────────────────────────
  Title: feat(routes): add task filtering by status, date range and search

  Base: develop <- (merge vers develop, pas main!)
  Compare: feature/5-task-filtering

  Body (template pré-rempli):
  ## Description
  Implémentation du filtrage des tâches via query parameters.
  - Filtrage par statut done/not done: ?done=true/false
  - Filtrage par plage de dates: ?from=YYYY-MM-DD&to=YYYY-MM-DD
  - Recherche dans le titre: ?search=keyword
  - Pagination: ?page=N&per_page=N

  ## Type de changement
  - [x] Nouvelle fonctionnalité (ajout non cassant)

  ## Tests
  - [x] J'ai ajouté des tests qui prouvent que la feature fonctionne
  - [x] Les tests existants passent avec mes changements
  - [x] Coverage: routes.py 96%

  ## Issues liées
  Closes #5

  Labels: enhancement, flask
  Assignees: @bob-dev
  Reviewers: @alice-dev, @claire-dev
  Milestone: v1.0.0

  -> Create pull request -> PR #6 créée

──────────────────────────────────────────────────────────────────────────────
A.7 - Alice et Claire font la code review
──────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GitHub (onglet "Files changed" de la PR #6):

  Alice commence la review:
  ─────────────────────────
  Alice lit le code ligne par ligne. Elle clique sur le "+" à gauche d'une ligne
  pour ajouter un commentaire.

  Commentaire d'Alice sur routes.py ligne 45:
  ┌──────────────────────────────────────────────────────────────────────┐
  │ @bob-dev Bonne implémentation! Suggestion: extraire la logique de    │
  │ validation des dates dans utils.py pour la réutilisabilité.          │
  │                                                                      │
  │ ```python                                                            │
  │ # utils.py                                                           │
  │ def parse_date(date_str: str) -> datetime:                          │
  │     try:                                                             │
  │         return datetime.strptime(date_str, '%Y-%m-%d')              │
  │     except ValueError:                                              │
  │         raise ValueError(f"Invalid date format: {date_str}")        │
  │ ```                                                                  │
  │ [Start a review]                                                     │
  └──────────────────────────────────────────────────────────────────────┘

  Commentaire d'Alice sur routes.py ligne 67:
  ┌──────────────────────────────────────────────────────────────────────┐
  │ per_page devrait avoir une limite maximale pour éviter les abus.     │
  │ ```python                                                            │
  │ per_page = min(request.args.get('per_page', 20, type=int), 100)     │
  │ ```                                                                  │
  └──────────────────────────────────────────────────────────────────────┘

  Alice soumet sa review:
  -> Review changes -> "Request changes" (pas encore approuvé)
  -> Submit review

  Commentaire de Claire sur test_routes.py:
  ──────────────────────────────────────────
  ┌──────────────────────────────────────────────────────────────────────┐
  │ Manque un test pour la combinaison de filtres:                       │
  │ ?done=false&search=urgent                                            │
  │ Ça devrait être testé car c'est le cas d'usage principal.            │
  └──────────────────────────────────────────────────────────────────────┘

  Claire soumet: "Request changes"

  Bob répond aux commentaires:
  ─────────────────────────────
  BOB dans son terminal:

  # Bob fait les modifications demandées:
  # 1. Extraire la validation de dates dans utils.py
  # 2. Ajouter limite max per_page
  # 3. Ajouter test de combinaison de filtres

  git add app/utils.py app/routes.py tests/test_routes.py
  git commit -m "refactor(routes): address PR review comments

  - Extract date parsing to utils.parse_date()
  - Add max per_page limit (100) to prevent abuse
  - Add test for combined filters (done + search)

  Co-authored-by: Alice Martin <alice@equipe.com>"
  git push origin feature/5-task-filtering

  Sur GitHub, Bob répond à chaque commentaire:
  -> Cliquer "Resolve conversation" sur chaque commentaire adressé
  -> Re-request review des reviewers

  Alice et Claire voient le nouveau push et re-reviewent:
  ─────────────────────────────────────────────────────────
  Alice: "Review changes" -> "Approve" [OK]
  -> "LGTM! Toutes les suggestions ont été implémentées. Merge autorisé."

  Claire: "Review changes" -> "Approve" [OK]
  -> "Tests complets et bien organisés. [OK]"

──────────────────────────────────────────────────────────────────────────────
A.8 - Merge de la Pull Request
──────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GitHub (PR #6):

  Tous les checks sont au vert:
  [OK] ci/tests (14/14 tests passés, coverage 96%)
  [OK] ci/sonarqube-quality-gate (A)
  [OK] ci/build-docker (success)
  [OK] 2 approvals from reviewers
  [OK] All conversations resolved

  Alice clique "Squash and merge":
  -> Option "Squash and merge" -> Tous les commits de la feature sont
    fusionnés en UN SEUL commit propre dans develop

  Message du squash commit (auto-généré, Alice peut l'éditer):
  feat(routes): add task filtering by status, date range and search (#6)

  * feat(routes): add task filtering by status, date range and search
  * test(routes): add tests for task filtering
  * refactor(routes): address PR review comments

  Co-authored-by: Bob Dupont <bob@equipe.com>

  -> Confirm squash and merge

  GitHub propose automatiquement:
  -> "Delete branch" -> Alice clique <- Nettoie la branche feature distante

  ALTERNATIVES AU SQUASH MERGE:
  ───────────────────────────────
  - Merge commit: Crée un merge commit, conserve tous les commits individuels
    -> Utilisé pour develop -> main (traçabilité complète)

  - Squash and merge: Compresse en 1 commit
    -> Utilisé pour feature -> develop (historique propre de develop)

  - Rebase and merge: Place les commits de la feature en tête de develop
    -> Historique linéaire, pas de merge commit

  BOB nettoie sa branche locale:
  ───────────────────────────────
  git checkout develop
  git pull origin develop          <- Récupérer le merge commit
  git branch -d feature/5-task-filtering  <- Supprimer la branche locale

  SORTIE:
  ───────
  Deleted branch feature/5-task-filtering (was b7d2e4f).

  # Supprimer aussi la référence au remote supprimé:
  git fetch --prune
  # ou:
  git remote prune origin


════════════════════════════════════════════════════════════════════════════════
SCÉNARIO B - Conflit de merge entre Bob et Claire
════════════════════════════════════════════════════════════════════════════════

Bob et Claire travaillent simultanément sur routes.py.
Un conflit de merge est inévitable. Voici comment le résoudre.

──────────────────────────────────────────────────────────────────────────────
B.1 - Bob et Claire créent leurs branches en même temps
──────────────────────────────────────────────────────────────────────────────

BOB crée feature/6-task-priority:
  git checkout develop
  git pull origin develop
  git checkout -b feature/6-task-priority
  git push -u origin feature/6-task-priority

CLAIRE crée feature/7-task-tags:
  git checkout develop
  git pull origin develop
  git checkout -b feature/7-task-tags
  git push -u origin feature/7-task-tags

──────────────────────────────────────────────────────────────────────────────
B.2 - Bob et Claire modifient le même fichier (routes.py)
──────────────────────────────────────────────────────────────────────────────

BOB modifie l'en-tête de la fonction get_tasks() dans routes.py:
  # Bob ajoute le paramètre 'priority':
  priority = request.args.get('priority')  # 'low', 'medium', 'high'
  if priority:
      query = query.filter(Task.priority == priority)

  git add app/routes.py
  git commit -m "feat(routes): add priority filter to GET /tasks"
  git push origin feature/6-task-priority

  # Bob ouvre une PR et elle est mergée dans develop en premier.

CLAIRE modifie la même fonction dans routes.py:
  # Claire ajoute les tags:
  tags = request.args.get('tags')  # ex: "urgent,backend"
  if tags:
      tag_list = tags.split(',')
      query = query.filter(Task.tags.overlap(tag_list))

  git add app/routes.py
  git commit -m "feat(routes): add tags filter to GET /tasks"
  git push origin feature/7-task-tags

──────────────────────────────────────────────────────────────────────────────
B.3 - Claire tente de rebaser sur develop (conflit!)
──────────────────────────────────────────────────────────────────────────────

CLAIRE dans son terminal:

  # Récupérer les derniers changements (inclut le merge de Bob):
  git fetch origin

  # Rebaser sur develop:
  git rebase origin/develop

  SORTIE (conflit!):
  ──────────────────
  Auto-merging app/routes.py
  CONFLICT (content): Merge conflict in app/routes.py
  error: could not apply c8f2d1a... feat(routes): add tags filter to GET /tasks
  hint: Resolve all conflicts manually, mark them as resolved with
  hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
  hint: You can instead skip this commit: run "git rebase --skip".
  hint: To abort and get back to pre-rebase state, run "git rebase --abort".

  # Voir les fichiers en conflit:
  git status
  # -> both modified: app/routes.py

  CONTENU DE routes.py AVEC LES MARQUEURS DE CONFLIT:
  ─────────────────────────────────────────────────────
  @tasks_bp.route('/tasks', methods=['GET'])
  def get_tasks():
      query = Task.query

      # Filtre par statut
      done_param = request.args.get('done')
      if done_param is not None:
          done = done_param.lower() == 'true'
          query = query.filter(Task.done == done)

  <<<<<<< HEAD
      # Bob: Filtre par priorité
      priority = request.args.get('priority')
      if priority:
          query = query.filter(Task.priority == priority)
  =======
      # Claire: Filtre par tags
      tags = request.args.get('tags')
      if tags:
          tag_list = tags.split(',')
          query = query.filter(Task.tags.overlap(tag_list))
  >>>>>>> c8f2d1a (feat(routes): add tags filter to GET /tasks)

      # Pagination
      ...

  EXPLICATION DES MARQUEURS:
  ───────────────────────────
  <<<<<<< HEAD           -> Début du conflit - version du develop (Bob)
  =======                -> Séparateur entre les deux versions
  >>>>>>> c8f2d1a...     -> Fin du conflit - version de Claire

  Claire résout le conflit (garde LES DEUX):
  ────────────────────────────────────────────
  # Ouvrir routes.py dans l'éditeur et modifier:
  nano app/routes.py

  # Résolution: garder la priorité ET les tags (les deux sont valides):
  @tasks_bp.route('/tasks', methods=['GET'])
  def get_tasks():
      query = Task.query

      # Filtre par statut
      done_param = request.args.get('done')
      if done_param is not None:
          done = done_param.lower() == 'true'
          query = query.filter(Task.done == done)

      # Filtre par priorité (ajouté par Bob - feature/6)
      priority = request.args.get('priority')
      if priority:
          query = query.filter(Task.priority == priority)

      # Filtre par tags (ajouté par Claire - feature/7)
      tags = request.args.get('tags')
      if tags:
          tag_list = tags.split(',')
          query = query.filter(Task.tags.overlap(tag_list))

      # Pagination
      ...

  # NB: TOUS LES MARQUEURS <<<<<, =====, >>>>> doivent être SUPPRIMÉS!

  # Marquer le conflit comme résolu:
  git add app/routes.py

  # Continuer le rebase:
  git rebase --continue

  # Git ouvre l'éditeur pour le message du commit:
  # Claire valide le message existant

  SORTIE:
  ───────
  Successfully rebased and updated refs/heads/feature/7-task-tags.

  # Push forcé (rebase a réécrit l'historique):
  git push --force-with-lease origin feature/7-task-tags

  OUTILS POUR RÉSOUDRE LES CONFLITS:
  ─────────────────────────────────────
  # Utiliser un outil visuel de merge:
  git mergetool                           <- Ouvre l'outil configuré

  # Configurer VS Code comme outil de merge:
  git config --global merge.tool vscode
  git config --global mergetool.vscode.cmd \
    'code --wait $MERGED'

  # Configurer vimdiff:
  git config --global merge.tool vimdiff

  # Voir les conflits en détail:
  git diff                                <- Voir tous les conflits
  git diff --diff-filter=U               <- Seulement les fichiers en conflit

  # En cas de désastre - tout annuler et recommencer:
  git rebase --abort                      <- Annuler le rebase en cours
  git merge --abort                       <- Annuler le merge en cours
  git checkout -- app/routes.py           <- Annuler les modifs d'un fichier


════════════════════════════════════════════════════════════════════════════════
SCÉNARIO C - Alice fait un hotfix urgent en production
════════════════════════════════════════════════════════════════════════════════

Un bug critique est découvert en production (main): l'endpoint POST /tasks
crashe si la description est un objet JSON au lieu d'une chaîne.

──────────────────────────────────────────────────────────────────────────────
C.1 - Alice crée une issue et une branche hotfix depuis main
──────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GitHub: Issues -> New issue -> Bug Report

  Title: [BUG] POST /tasks crashes with 500 if description is not a string
  Body: ...
  Labels: bug, priority: high
  Assignees: @alice-dev
  -> Issue #8

ALICE dans son terminal:

  # IMPORTANT: partir de main (production), pas develop!
  git checkout main
  git pull origin main

  # Créer la branche hotfix:
  git checkout -b hotfix/8-fix-task-description-type

  # Corriger le bug dans routes.py:
  # Ajouter validation du type de description
  # ...

  git add app/routes.py tests/test_routes.py
  git commit -m "fix(routes): validate description type in POST /tasks

  - Add type validation for description field
  - Return 400 if description is not a string
  - Add test for invalid description type

  Fixes #8"

──────────────────────────────────────────────────────────────────────────────
C.2 - Alice merge le hotfix dans main ET dans develop
──────────────────────────────────────────────────────────────────────────────

  git push -u origin hotfix/8-fix-task-description-type

  # PR #9 de hotfix -> main (review rapide par Bob)
  # Après approbation et merge:

  # Aussi merger dans develop pour que develop soit à jour:
  git checkout develop
  git pull origin develop
  git merge --no-ff hotfix/8-fix-task-description-type
  git push origin develop

  # Supprimer la branche hotfix:
  git branch -d hotfix/8-fix-task-description-type
  git push origin --delete hotfix/8-fix-task-description-type

  # Créer un tag pour le hotfix:
  git checkout main
  git tag -a v1.0.1 -m "hotfix: fix task description type validation

  Fixes critical bug #8: POST /tasks crashed with 500 when description
  was a JSON object instead of a string.
  "
  git push origin v1.0.1


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 4 - GESTION DES TAGS ET RELEASES
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 4.1 - Alice prépare la release v1.0.0
────────────────────────────────────────────────────────────────────────────────

  # Créer la branche de release:
  git checkout develop
  git pull origin develop
  git checkout -b release/1.0.0

  # Bumper la version dans config.py:
  nano config.py
  # APP_VERSION = "1.0.0"

  git add config.py
  git commit -m "chore(release): bump version to 1.0.0"

  # Derniers tests sur la branche de release:
  docker-compose -f docker-compose.test.yml run --rm test

  # Si tout est OK: merger dans main:
  git checkout main
  git pull origin main
  git merge --no-ff release/1.0.0 -m "chore: merge release/1.0.0 into main"
  git push origin main

  # Merger aussi dans develop:
  git checkout develop
  git merge --no-ff release/1.0.0 -m "chore: merge release/1.0.0 back into develop"
  git push origin develop

  # Créer le tag annoté:
  git checkout main
  git tag -a v1.0.0 -m "Release v1.0.0 - MVP

  Features:
  - CRUD complet pour les tâches (GET, POST, PUT, DELETE)
  - Filtrage par statut, dates et mots-clés
  - Filtrage par priorité
  - Filtrage par tags
  - Pagination des résultats
  - Endpoint /health pour monitoring
  - Logging structuré (JSON)
  - Couverture de tests: 95%

  Bug fixes:
  - Fix description type validation (#8)

  Breaking changes:
  - GET /tasks now returns paginated results (was a simple list)
    Migration: use response.tasks instead of response directly"

  # Pousser le tag:
  git push origin v1.0.0

  # Pousser TOUS les tags à la fois:
  git push origin --tags

  TYPES DE TAGS:
  ───────────────
  # Tag annoté (recommandé): contient auteur, date, message
  git tag -a v1.0.0 -m "Release v1.0.0"

  # Tag léger: juste un pointeur sur un commit (pas de métadonnées)
  git tag v1.0.0-beta

  # Tagger un commit ancien:
  git tag -a v0.9.0 a3f8c1d -m "Version 0.9 beta"

  GÉRER LES TAGS:
  ────────────────
  git tag                              <- Lister les tags
  git tag -l "v1.*"                   <- Tags correspondant au pattern
  git show v1.0.0                     <- Détails d'un tag
  git tag -d v1.0.0                   <- Supprimer un tag local
  git push origin --delete v1.0.0     <- Supprimer un tag remote

  SE PLACER SUR UN TAG (mode détaché):
  ─────────────────────────────────────
  git checkout v1.0.0
  # -> Note: switching to 'v1.0.0'.
  #   You are in 'detached HEAD' state.
  #   HEAD se trouve maintenant sur le commit tagué
  #   Pour créer une branche depuis ce point:
  git checkout -b hotfix/from-v1.0.0

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 4.2 - Créer une Release GitHub depuis le tag
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GitHub: Releases -> Create a new release

  Tag: v1.0.0 (existant)
  Release title: v1.0.0 - MVP - TaskManager API

  Release notes:
  ## [BRAVO] TaskManager API v1.0.0 - MVP

  Première release stable de l'API de gestion de tâches.

  ### * Nouveautés
  - **CRUD tâches**: Créer, lire, modifier, supprimer des tâches
  - **Filtrage avancé**: Par statut, dates, mots-clés, priorité, tags
  - **Pagination**: Résultats paginés avec métadonnées
  - **Health check**: Endpoint /health pour le monitoring

  ### [BUG] Corrections
  - Fix: Validation du type de description (#8)

  ### [ATTENTION] Breaking Changes
  - `GET /tasks` retourne maintenant un objet paginé:
    ```json
    {
      "tasks": [...],
      "total": 42,
      "page": 1,
      "per_page": 20,
      "pages": 3
    }
    ```
    Avant: retournait directement `[...]`

  ### [PACKAGE] Installation
  ```bash
  docker pull ghcr.io/equipe/taskmanager-api:1.0.0
  docker-compose -f docker-compose.prod.yml up -d
  ```

  ### [GRAPHIQUE] Qualité
  - Tests: 14/14 [OK]
  - Coverage: 95% [OK]
  - SonarQube: A [OK]

  Attachments: (GitHub Actions attache automatiquement les artefacts)
  - taskmanager-api-v1.0.0.tar.gz (code source)

  Set as latest release: [x]
  -> Publish release

  Cela déclenche le workflow GitHub Actions "deploy-production"!


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 5 - FONCTIONNALITÉS GIT AVANCÉES
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 5.1 - git bisect: trouver quel commit a introduit un bug
────────────────────────────────────────────────────────────────────────────────

Un bug est apparu quelque part entre v1.0.0 et la version actuelle.
Bob utilise git bisect pour trouver exactement quel commit l'a introduit.

BOB dans son terminal:

  # Démarrer la bisection:
  git bisect start

  # Marquer le commit actuel comme "mauvais" (le bug existe):
  git bisect bad HEAD

  # Marquer v1.0.0 comme "bon" (le bug n'existait pas):
  git bisect good v1.0.0

  SORTIE:
  ───────
  Bisecting: 7 revisions left to test after this (roughly 3 steps)
  [m3d4e5f] feat(routes): add priority filter to GET /tasks

  # Git se place automatiquement au milieu de l'historique
  # Bob teste si le bug existe là:
  pytest tests/test_routes.py -v

  # Si le bug EXISTE dans ce commit:
  git bisect bad

  # Si le bug N'EXISTE PAS dans ce commit:
  git bisect good

  # Git continue à diviser l'intervalle:
  SORTIE (après plusieurs itérations):
  ─────────────────────────────────────
  Bisecting: 1 revision left to test after this (roughly 1 step)
  [c8f2d1a] feat(routes): add tags filter to GET /tasks
  ...
  [f9g3h4i] refactor(models): change Task.tags from List to Set

  b2c3d4e is the first bad commit
  commit b2c3d4e
  Author: Bob Dupont <bob@equipe.com>
  Date:   Mon Jan 15 14:23:00 2024 +0100

      refactor(models): change Task.tags from List to Set

  # Bob a trouvé le commit responsable!

  # Terminer la bisection et revenir à HEAD:
  git bisect reset

  # AUTOMATISER avec un script de test:
  git bisect start
  git bisect bad HEAD
  git bisect good v1.0.0
  git bisect run pytest tests/test_routes.py::test_task_tags -q
  # Git exécute automatiquement le test sur chaque commit
  # Exit 0 = bon, exit != 0 = mauvais

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 5.2 - git cherry-pick: appliquer un commit spécifique
────────────────────────────────────────────────────────────────────────────────

Contexte: Alice a corrigé un bug dans develop mais le bug existe aussi dans main.
Elle ne veut pas merger tout develop -> main, juste le commit de fix.

ALICE dans son terminal:

  # Trouver le hash du commit de fix dans develop:
  git log develop --oneline | grep "fix:"
  # -> d4e5f6g fix(routes): validate priority values against allowed list

  # Se placer sur main:
  git checkout main
  git pull origin main

  # Appliquer seulement ce commit sur main:
  git cherry-pick d4e5f6g

  SORTIE:
  ───────
  [main h8i9j0k] fix(routes): validate priority values against allowed list
   Date: Mon Jan 15 14:23:00 2024 +0100
   1 file changed, 5 insertions(+), 2 deletions(-)

  git push origin main

  # Cherry-pick d'une plage de commits:
  git cherry-pick d4e5f6g..a1b2c3d    <- De d4e5f6g à a1b2c3d (exclusif)
  git cherry-pick d4e5f6g^..a1b2c3d   <- Inclure d4e5f6g aussi

  # Cherry-pick sans committer (pour inspecter d'abord):
  git cherry-pick d4e5f6g --no-commit
  # -> Applique les changements mais ne committe pas
  git diff                              <- Inspecter
  git commit -m "cherry-pick: ..."     <- Committer manuellement

  # En cas de conflit:
  git cherry-pick d4e5f6g
  # CONFLICT...
  # Résoudre le conflit...
  git add app/routes.py
  git cherry-pick --continue
  # Ou annuler:
  git cherry-pick --abort

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 5.3 - git revert: annuler un commit sans réécrire l'historique
────────────────────────────────────────────────────────────────────────────────

Un commit mergé dans main cause des problèmes. On ne peut pas réécrire
l'historique de main (branche partagée et protégée), donc on utilise revert.

ALICE dans son terminal:

  # Identifier le commit problématique:
  git log main --oneline
  # -> j1k2l3m feat(routes): add rate limiting (<- CELUI-CI CAUSE DES PROBLÈMES)
  # -> h8i9j0k fix(routes): validate priority values
  # -> a3f8c1d feat: initial project setup

  # Créer un commit qui inverse j1k2l3m:
  git revert j1k2l3m

  # Git ouvre l'éditeur avec un message pré-rempli:
  Revert "feat(routes): add rate limiting"

  This reverts commit j1k2l3m.

  Reason: Rate limiting causes issues with our internal monitoring
  agent. Will be re-implemented with whitelist support.

  Ref: #15

  # Sauvegarder -> commit créé:
  [main p4q5r6s] Revert "feat(routes): add rate limiting"
   1 file changed, 15 deletions(-)

  git push origin main

  # La différence avec git reset:
  # git revert -> CRÉE un nouveau commit qui inverse -> conserve l'historique
  # git reset  -> SUPPRIME des commits -> réécrit l'historique (dangereux!)

  # Revert de plusieurs commits:
  git revert HEAD~3..HEAD    <- Annuler les 3 derniers commits
  git revert --no-commit HEAD~3..HEAD  <- Sans créer des commits individuels
  git commit -m "revert: rollback last 3 commits"

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 5.4 - git reset: annuler des commits locaux (NON partagés)
────────────────────────────────────────────────────────────────────────────────

Bob a fait 3 commits "brouillons" localement et veut les nettoyer
AVANT de pousser (ces commits ne sont pas encore sur GitHub).

BOB dans son terminal:

  git log --oneline
  # -> c3d4e5f WIP: debuggage (<- mauvais commit)
  # -> b2c3d4e WIP: encore des tests (<- mauvais commit)
  # -> a1b2c3d feat(models): add Task.priority field (<- CELUI-CI est bon)
  # -> j1k2l3m [commit précédent de develop]

  # Revenir 3 commits en arrière SANS perdre les modifications:
  git reset --soft HEAD~3

  # -> HEAD pointe maintenant sur j1k2l3m
  # -> Toutes les modifications des 3 commits sont dans le STAGE
  # -> Bob peut maintenant faire UN commit propre:

  git status
  # -> Changes to be committed:
  #     modified: app/models.py
  #     modified: tests/test_models.py

  git commit -m "feat(models): add Task.priority field with validation

  - Add priority field: low, medium, high
  - Default priority: medium
  - Validation via SQLAlchemy CheckConstraint
  - Add to Task.to_dict() output
  - Tests for priority validation"

  MODES DE RESET:
  ────────────────
  git reset --soft HEAD~1   -> Défait le commit, garde les modifs STAGÉES
  git reset --mixed HEAD~1  -> Défaut: défait le commit, garde les modifs NON stagées
  git reset --hard HEAD~1   -> Défait le commit ET SUPPRIME les modifs (DANGEREUX!)

  # --hard supprime définitivement les modifications non commitées!
  # Utiliser avec EXTRÊME prudence.

  # Récupérer après un --hard accidentel:
  git reflog                     <- Voir l'historique de tous les mouvements de HEAD
  git reset --hard abc123def     <- Revenir à cet état

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 5.5 - git reflog: la bouée de sauvetage ultime
────────────────────────────────────────────────────────────────────────────────

Bob a fait un git reset --hard par erreur et a perdu des commits!
Le reflog garde une trace de TOUS les mouvements de HEAD.

BOB dans son terminal:

  git reflog

  SORTIE:
  ───────
  j1k2l3m HEAD@{0}: reset: moving to HEAD~3  <- reset --hard d'il y a 1 minute
  c3d4e5f HEAD@{1}: commit: WIP: debuggage
  b2c3d4e HEAD@{2}: commit: WIP: encore des tests
  a1b2c3d HEAD@{3}: commit: feat(models): add Task.priority field
  j1k2l3m HEAD@{4}: checkout: moving from develop to feature/8-priority

  # Bob voit que HEAD@{3} = a1b2c3d est son bon commit
  # Il peut revenir dessus:
  git reset --hard HEAD@{3}
  # Ou:
  git reset --hard a1b2c3d

  # Les commits "perdus" sont récupérés!

  # IMPORTANT: git reflog ne dure que 90 jours par défaut
  # Après, les commits orphelins sont supprimés par git gc

  # Voir le reflog d'une branche spécifique:
  git reflog show develop
  git reflog show HEAD

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 5.6 - git rebase interactif: réécrire l'historique local
────────────────────────────────────────────────────────────────────────────────

Claire a 5 commits "brouillons" sur sa feature branch et veut les nettoyer
en les réorganisant, les fusionnant et en améliorant les messages.

CLAIRE dans son terminal:

  git log --oneline
  # -> e5f6g7h fix: typo in test
  # -> d4e5f6g WIP: tests
  # -> c3d4e5f add more validation
  # -> b2c3d4e feat(routes): add task tags
  # -> a1b2c3d [last commit on develop]

  # Rebase interactif sur les 4 derniers commits:
  git rebase -i HEAD~4

  # Git ouvre l'éditeur avec:
  pick b2c3d4e feat(routes): add task tags
  pick c3d4e5f add more validation
  pick d4e5f6g WIP: tests
  pick e5f6g7h fix: typo in test

  # Commandes disponibles:
  # p, pick   = utiliser le commit tel quel
  # r, reword = utiliser le commit mais modifier le message
  # e, edit   = utiliser le commit mais s'arrêter pour amender
  # s, squash = fusionner avec le commit précédent
  # f, fixup  = fusionner avec le précédent (supprimer ce message)
  # x, exec   = exécuter une commande shell
  # b, break  = s'arrêter ici
  # d, drop   = supprimer ce commit
  # l, label  = étiqueter pour utilisation avec merge
  # t, reset  = réinitialiser HEAD à un label
  # m, merge  = créer un merge commit

  Claire modifie le fichier:
  ────────────────────────────
  pick b2c3d4e feat(routes): add task tags
  squash c3d4e5f add more validation    <- Fusionner avec b2c3d4e
  squash d4e5f6g WIP: tests             <- Fusionner avec b2c3d4e
  fixup e5f6g7h fix: typo in test       <- Fusionner sans garder le message

  # Sauvegarder et fermer l'éditeur

  # Git ouvre un 2ème éditeur pour le message du commit fusionné:
  feat(routes): add task tags with filtering and validation

  - Add tags field to Task model (PostgreSQL array)
  - Add ?tags=tag1,tag2 filter to GET /tasks
  - Validate tags: lowercase, alphanumeric, max 20 tags
  - Tests for tag filtering and validation
  - Coverage: routes.py 97%, models.py 100%

  # Sauvegarder -> un seul commit propre créé!

  git log --oneline
  # -> f6g7h8i feat(routes): add task tags with filtering and validation
  # -> a1b2c3d [last commit on develop]

  # ATTENTION: Ne jamais rebase -i sur des commits déjà poussés sur remote!
  # (Réécriture de l'historique partagé = chaos pour les autres)
  # Si nécessaire: push --force-with-lease mais prévenir l'équipe!

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 5.7 - git submodules: dépendances Git dans Git
────────────────────────────────────────────────────────────────────────────────

L'équipe décide d'extraire leurs utilitaires Flask dans un dépôt séparé
réutilisable par d'autres projets.

  AJOUTER UN SUBMODULE:
  ──────────────────────
  # Alice crée le dépôt equipe/flask-utils sur GitHub

  # Ajouter le submodule dans taskmanager:
  git submodule add git@github.com:equipe/flask-utils.git lib/flask-utils

  SORTIE:
  ───────
  Cloning into '/home/alice/taskmanager/lib/flask-utils'...
  remote: Enumerating objects: 12, done.

  # Deux nouveaux fichiers créés:
  # .gitmodules  <- Configuration des submodules
  # lib/flask-utils/ <- Dossier du submodule (pointe sur un commit spécifique)

  CONTENU DE .gitmodules:
  ────────────────────────
  [submodule "lib/flask-utils"]
      path = lib/flask-utils
      url = git@github.com:equipe/flask-utils.git

  git add .gitmodules lib/flask-utils
  git commit -m "chore: add flask-utils as submodule"
  git push

  CLONER UN DÉPÔT AVEC SUBMODULES:
  ──────────────────────────────────
  # Méthode 1: avec --recursive
  git clone --recursive git@github.com:equipe/taskmanager.git

  # Méthode 2: après un clone normal
  git clone git@github.com:equipe/taskmanager.git
  cd taskmanager
  git submodule init
  git submodule update

  METTRE À JOUR UN SUBMODULE:
  ────────────────────────────
  cd lib/flask-utils
  git pull origin main
  cd ../..
  git add lib/flask-utils
  git commit -m "chore: update flask-utils to latest"

  # Mettre à jour TOUS les submodules:
  git submodule update --remote --merge

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 5.8 - git hooks: automatisation locale
────────────────────────────────────────────────────────────────────────────────

Les hooks sont des scripts exécutés automatiquement par Git.
Ils sont dans .git/hooks/ (local, pas versionné par défaut).

  HOOK pre-commit: Lancer les tests avant chaque commit
  ──────────────────────────────────────────────────────
  nano .git/hooks/pre-commit

  Contenu:
  #!/bin/bash
  # Hook pre-commit: vérifications avant de permettre le commit

  echo "[RECHERCHE] Vérifications pre-commit..."

  # 1. Vérifier que pas de fichiers .env commités:
  if git diff --cached --name-only | grep -E "^\.env$|^\.env\.[^e]"; then
      echo "[X] ERREUR: Tentative de committer un fichier .env!"
      echo "   Ces fichiers contiennent des secrets et ne doivent JAMAIS être committés."
      exit 1
  fi

  # 2. Vérifier la syntaxe Python:
  echo "[PYTHON] Vérification syntaxe Python..."
  python -m py_compile app/*.py 2>&1
  if [ $? -ne 0 ]; then
      echo "[X] Erreur de syntaxe Python détectée!"
      exit 1
  fi

  # 3. Lancer flake8 (linter):
  echo "[RECHERCHE] Lint avec flake8..."
  if command -v flake8 &> /dev/null; then
      flake8 app/ tests/ --max-line-length=120 --exclude=__pycache__
      if [ $? -ne 0 ]; then
          echo "[X] Problèmes de style détectés par flake8!"
          echo "   Corriger ou utiliser git commit --no-verify pour bypass"
          exit 1
      fi
  fi

  echo "[OK] Vérifications pre-commit passées!"
  exit 0

  chmod +x .git/hooks/pre-commit

  HOOK commit-msg: Valider le format du message
  ───────────────────────────────────────────────
  nano .git/hooks/commit-msg

  Contenu:
  #!/bin/bash
  # Valider que le message de commit suit Conventional Commits

  commit_message=$(cat "$1")
  pattern="^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?: .{1,72}$"

  # Ignorer les commits de merge, squash, etc.
  if echo "$commit_message" | grep -qE "^(Merge|Revert|Squash)"; then
      exit 0
  fi

  # Ignorer la première ligne des commits multi-lignes
  first_line=$(echo "$commit_message" | head -1)

  if ! echo "$first_line" | grep -qE "$pattern"; then
      echo "[X] Message de commit invalide!"
      echo ""
      echo "   Format requis: <type>(<scope>): <description>"
      echo "   Types: feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert"
      echo ""
      echo "   Exemples:"
      echo "   feat(routes): add task priority filtering"
      echo "   fix(models): handle null description"
      echo "   test(routes): add tests for filtering"
      echo ""
      echo "   Votre message: $first_line"
      exit 1
  fi

  exit 0

  chmod +x .git/hooks/commit-msg

  HOOK pre-push: Lancer les tests avant chaque push
  ────────────────────────────────────────────────────
  nano .git/hooks/pre-push

  Contenu:
  #!/bin/bash
  # Lancer les tests avant d'autoriser le push

  echo "[TEST] Lancement des tests avant push..."

  # Lancer pytest directement (hors Docker, plus rapide):
  python -m pytest tests/ -q --tb=short

  if [ $? -ne 0 ]; then
      echo ""
      echo "[X] Des tests échouent! Push bloqué."
      echo "   Corriger les tests ou utiliser git push --no-verify pour bypass"
      exit 1
  fi

  echo "[OK] Tous les tests passent. Push autorisé!"
  exit 0

  chmod +x .git/hooks/pre-push

  PARTAGER LES HOOKS AVEC L'ÉQUIPE (avec pre-commit framework):
  ─────────────────────────────────────────────────────────────
  # Les hooks .git/hooks/ ne sont pas versionnés.
  # Solution: utiliser le framework "pre-commit" (pip install pre-commit)

  nano .pre-commit-config.yaml

  Contenu:
  repos:
    - repo: https://github.com/pre-commit/pre-commit-hooks
      rev: v4.5.0
      hooks:
        - id: trailing-whitespace
        - id: end-of-file-fixer
        - id: check-yaml
        - id: check-json
        - id: check-added-large-files
          args: ['--maxkb=500']
        - id: detect-private-key
        - id: no-commit-to-branch
          args: ['--branch', 'main', '--branch', 'develop']

    - repo: https://github.com/psf/black
      rev: 23.12.1
      hooks:
        - id: black
          language_version: python3.11

    - repo: https://github.com/pycqa/flake8
      rev: 6.1.0
      hooks:
        - id: flake8
          args: ['--max-line-length=120']

    - repo: https://github.com/compilerla/conventional-pre-commit
      rev: v3.0.0
      hooks:
        - id: conventional-pre-commit
          stages: [commit-msg]

  # Installer les hooks pour tous les membres de l'équipe:
  pip install pre-commit
  pre-commit install
  pre-commit install --hook-type commit-msg

  git add .pre-commit-config.yaml
  git commit -m "chore: add pre-commit hooks configuration"

  # Les hooks s'exécutent automatiquement sur git commit pour chaque dev!

  # Exécuter manuellement sur tous les fichiers:
  pre-commit run --all-files

  # Bypass occasionnel (à utiliser avec parcimonie!):
  git commit --no-verify -m "chore: emergency fix"


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 6 - GITHUB: FONCTIONNALITÉS DE COLLABORATION
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 6.1 - GitHub Issues: gestion du backlog
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GitHub: onglet "Issues"

  CRÉER UNE ISSUE AVEC TÂCHES IMBRIQUÉES:
  ─────────────────────────────────────────
  Title: [FEAT] User authentication system

  Body:
  ## Description
  Implémenter l'authentification JWT pour l'API.

  ## Tâches
  - [ ] Modèle User (SQLAlchemy)
  - [ ] Endpoint POST /auth/register
  - [ ] Endpoint POST /auth/login (retourne JWT)
  - [ ] Middleware d'authentification
  - [ ] Endpoint GET /auth/me (profil utilisateur)
  - [ ] Endpoint POST /auth/logout
  - [ ] Tests d'authentification
  - [ ] Documentation Swagger/OpenAPI

  Assignees: @bob-dev, @claire-dev
  Labels: enhancement, priority: high
  Milestone: v1.1.0
  Projects: TaskManager Roadmap

  GitHub affiche: "0 of 8 tasks" -> barre de progression automatique!

  LIER DES ISSUES:
  ─────────────────
  # Dans une issue, écrire:
  "Ce bug est lié à #5" -> lien automatique vers l'issue #5
  "Duplique de #3"      -> mention de l'issue #3
  "Bloqué par #10"      -> relation de blocage

  # Dans un commit ou une PR:
  "Fixes #9"   -> Ferme automatiquement l'issue #9 lors du merge
  "Closes #9"  -> Idem
  "Resolves #9" -> Idem

  FERMER UNE ISSUE VIA COMMIT:
  ─────────────────────────────
  git commit -m "feat(auth): add JWT authentication

  - Implement User model with hashed passwords
  - Add /auth/register, /auth/login, /auth/me endpoints
  - Add JWT middleware decorator
  - Tests: 22 passed, coverage 94%

  Closes #9"

  # Au moment du merge de la PR dans main -> issue #9 fermée automatiquement!

  UTILISER LES MENTIONS:
  ───────────────────────
  # Dans une issue ou une PR, @mention pour notifier:
  "@alice-dev peux-tu relire la partie authentification?"
  "@bob-dev @claire-dev réunion de sync demain à 10h?"

  # GitHub envoie un email et une notification à la personne mentionnée

  ACTIONS SUR UNE ISSUE:
  ───────────────────────
  - Assign: Assigner à un membre de l'équipe
  - Labels: Ajouter des labels
  - Projects: Ajouter à un projet (kanban)
  - Milestone: Associer à un jalon
  - Link a pull request: Lier une PR à cette issue
  - Lock conversation: Empêcher de nouveaux commentaires
  - Pin issue: Épingler en haut de la liste
  - Transfer issue: Déplacer vers un autre dépôt
  - Convert to discussion: Si c'est plus une question qu'un bug

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 6.2 - GitHub Projects: tableau kanban de l'équipe
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GitHub: onglet "Projects" -> New project -> Board

  Alice crée le projet "TaskManager Roadmap":
  ──────────────────────────────────────────────

  Colonnes du tableau Kanban:
  ┌──────────────┬──────────────┬──────────────┬──────────────┬──────────────┐
  │   Backlog    │   To Do      │ In Progress  │ In Review    │    Done      │
  ├──────────────┼──────────────┼──────────────┼──────────────┼──────────────┤
  │ #9 Auth      │ #5 Filtering │ #8 Priority  │ #6 Tags      │ #1 CRUD      │
  │ #11 Swagger  │ #10 Export   │ (Bob)        │ (Claire/PR)  │ #2 Health    │
  │ #12 Webhooks │              │              │              │ #3 Logging   │
  └──────────────┴──────────────┴──────────────┴──────────────┴──────────────┘

  AUTOMATISATION DES PROJETS:
  ────────────────────────────
  Settings du projet -> Workflows:

  1. "Item added to project" -> Set status to "Backlog"
  2. "Item reopened" -> Set status to "To Do"
  3. "Pull request merged" -> Set status to "Done"
  4. "Item closed" -> Set status to "Done"
  5. "Auto-archive items" -> Archive after 2 weeks in Done

  VUE TABLE:
  ──────────
  Vue alternative en table avec colonnes personnalisées:
  - Title | Assignee | Status | Priority | Milestone | Estimate | Sprint

  VUE ROADMAP (timeline):
  ───────────────────────
  Vue Gantt pour visualiser les milestones dans le temps:
  Jan 2024: v1.0.0 [████████ DONE]
  Feb 2024: v1.1.0 [███░░░░░ IN PROGRESS]
  Mar 2024: v1.2.0 [░░░░░░░░ PLANNED]

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 6.3 - GitHub Discussions: communication d'équipe
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GitHub: onglet "Discussions"

  Catégories créées par Alice:
  ─────────────────────────────
  [ANNONCE] Announcements   -> Annonces importantes (seul Alice peut poster)
  [SPEECH_BALLOON] General         -> Discussions générales sur le projet
  [IDEE] Ideas           -> Propositions de nouvelles features
  [MERCI] Q&A             -> Questions techniques (avec réponses marquées)
  [BALLOT_BOX_WITH_BALLOT] Polls           -> Sondages de décision
  [GUIDE] Architecture    -> Décisions d'architecture (ADR)

  EXEMPLE DE DISCUSSION:
  ───────────────────────
  Title: [Architecture] Choix de la stratégie de cache Redis

  Alice poste une discussion:
  "Bonjour,

  On doit décider comment on utilise Redis pour le cache.
  J'ai identifié 3 approches:

  **Option A: Cache au niveau des routes (Flask-Caching)**
  - Pro: Simple à implémenter avec @cache.cached()
  - Con: Cache non invalidé intelligemment

  **Option B: Cache manuel dans les services**
  - Pro: Contrôle fin sur l'invalidation
  - Con: Plus de code à écrire

  **Option C: Cache au niveau de la base de données (query cache)**
  - Pro: Transparent pour l'application
  - Con: Complexité de configuration

  Votre avis?"

  Bob répond: "Je vote Option A pour la v1.2.0, on peut migrer vers B plus tard."
  Claire répond: "Option A +1. J'ai vu une bonne implémentation ici: [lien]"
  Alice marque la réponse de Bob comme "Marked as answer" [OK]

  -> La discussion devient une ADR (Architecture Decision Record) archivée

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 6.4 - GitHub Wiki: documentation du projet
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GitHub: onglet "Wiki" -> Create the first page

  Structure du Wiki créée par Alice:
  ────────────────────────────────────
  Home (index)
  ├── Installation et démarrage
  │   ├── Prérequis
  │   ├── Installation locale (avec Docker)
  │   └── Configuration .env
  ├── Architecture
  │   ├── Vue d'ensemble
  │   ├── Modèles de données
  │   └── Endpoints API
  ├── Développement
  │   ├── Workflow Git (branches, commits, PRs)
  │   ├── Conventions de code
  │   ├── Lancer les tests
  │   └── Débogage
  ├── Déploiement
  │   ├── Environnements (dev/staging/prod)
  │   ├── Procédure de release
  │   └── Rollback
  └── Runbooks
      ├── Problèmes courants
      ├── Sauvegarde/restauration DB
      └── Monitoring et alertes

  # Le Wiki est lui-même un dépôt Git!
  git clone git@github.com:equipe/taskmanager.wiki.git

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 6.5 - GitHub Security: sécurité du dépôt
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GitHub: onglet "Security"

  DEPENDABOT ALERTS (alertes de vulnérabilités):
  ────────────────────────────────────────────────
  Settings -> Security -> Code security and analysis

  [x] Dependency graph (activer)
  [x] Dependabot alerts (activer)
  [x] Dependabot security updates (activer)
  [x] Dependabot version updates (activer)

  Alice crée .github/dependabot.yml:

  version: 2
  updates:
    # Mettre à jour les dépendances Python
    - package-ecosystem: "pip"
      directory: "/"
      schedule:
        interval: "weekly"
        day: "monday"
        time: "09:00"
        timezone: "Europe/Paris"
      open-pull-requests-limit: 5
      assignees:
        - "alice-dev"
      labels:
        - "dependencies"
        - "chore"
      ignore:
        - dependency-name: "gunicorn"
          versions: [">=22.0.0"]  # Attendre la stabilisation

    # Mettre à jour les GitHub Actions
    - package-ecosystem: "github-actions"
      directory: "/"
      schedule:
        interval: "weekly"
      assignees:
        - "alice-dev"

  SECURITY POLICY (SECURITY.md):
  ────────────────────────────────
  nano SECURITY.md

  Contenu:
  # Politique de Sécurité

  ## Versions supportées
  | Version | Supportée |
  |---------|-----------|
  | 1.x.x   | [OK] Oui    |
  | < 1.0   | [X] Non    |

  ## Signaler une vulnérabilité
  NE PAS ouvrir une issue publique pour les failles de sécurité!

  Envoyer un email à: security@equipe.com
  Ou utiliser: GitHub -> Security -> Report a vulnerability

  On s'engage à répondre sous 48h et à fournir un patch sous 7 jours.

  SECRET SCANNING:
  ─────────────────
  Settings -> Security -> Secret scanning -> Enable
  -> GitHub scanne automatiquement les commits pour détecter
    des tokens, mots de passe, clés API accidentellement committés
  -> Si détecté: alerte immédiate + révocation automatique (certains providers)

  CODE SCANNING (CodeQL):
  ────────────────────────
  Security -> Code scanning -> Set up code scanning -> CodeQL

  Cela crée automatiquement .github/workflows/codeql.yml:
  -> Analyse statique du code Python pour détecter des vulnérabilités
  -> SQL injection, XSS, path traversal, etc.
  -> Exécuté à chaque push sur main et develop

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 6.6 - GitHub Insights: métriques du dépôt
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GitHub: onglet "Insights"

  PULSE (activité récente - 1 semaine):
  ───────────────────────────────────────
  ┌────────────────────────────────────────────────────────────────────┐
  │ Merged pull requests:   4                                          │
  │ Open pull requests:     2                                          │
  │ Closed issues:          6                                          │
  │ New issues:             3                                          │
  │ Commits:               18                                          │
  │                                                                    │
  │ Contributors: Alice (8 commits), Bob (6), Claire (4)              │
  └────────────────────────────────────────────────────────────────────┘

  CONTRIBUTORS:
  ──────────────
  Graphique de contributions dans le temps par auteur:
  - Nombre de commits
  - Lignes ajoutées/supprimées

  COMMIT ACTIVITY:
  ─────────────────
  Calendrier GitHub-style montrant l'activité de commit
  sur les 52 dernières semaines.

  CODE FREQUENCY:
  ────────────────
  Graphique semaines de lignes ajoutées (vert) vs supprimées (rouge).

  DEPENDENCY GRAPH:
  ──────────────────
  Visualisation des dépendances du projet et leurs dépendances transitives.
  Indique les vulnérabilités connues (Dependabot).

  FORKS:
  ───────
  Carte de tous les forks du dépôt et leur activité.

  NETWORK:
  ─────────
  Graphique de toutes les branches et forks avec leur relation.

  TRAFFIC:
  ─────────
  Nombre de vues et de clones du dépôt (14 derniers jours).


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 7 - GITHUB ACTIONS: CI/CD COMPLET
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 7.1 - Pipeline CI/CD complète
────────────────────────────────────────────────────────────────────────────────

  mkdir -p .github/workflows

  WORKFLOW PRINCIPAL (.github/workflows/ci.yml):
  ────────────────────────────────────────────────
  nano .github/workflows/ci.yml

  Contenu:
  name: CI - Tests, Lint, Coverage

  on:
    push:
      branches: ['**']             <- Sur toutes les branches
    pull_request:
      branches: [main, develop]

  jobs:

    lint:
      name: Lint et formatage
      runs-on: ubuntu-latest
      steps:
        - uses: actions/checkout@v4

        - name: Setup Python
          uses: actions/setup-python@v5
          with:
            python-version: '3.11'
            cache: 'pip'

        - name: Installer les outils de lint
          run: pip install black flake8 isort mypy

        - name: Vérifier le formatage Black
          run: black --check app/ tests/

        - name: Vérifier les imports avec isort
          run: isort --check-only app/ tests/

        - name: Lint avec flake8
          run: flake8 app/ tests/ --max-line-length=120

        - name: Type checking avec mypy
          run: mypy app/ --ignore-missing-imports

    tests:
      name: Tests Python
      runs-on: ubuntu-latest
      needs: lint

      services:
        postgres:
          image: postgres:15-alpine
          env:
            POSTGRES_DB: taskmanager_test
            POSTGRES_USER: test_user
            POSTGRES_PASSWORD: test_password
          options: >-
            --health-cmd pg_isready
            --health-interval 10s
            --health-timeout 5s
            --health-retries 5
          ports:
            - 5432:5432

        redis:
          image: redis:7-alpine
          options: >-
            --health-cmd "redis-cli ping"
            --health-interval 10s
            --health-timeout 5s
            --health-retries 5
          ports:
            - 6379:6379

      steps:
        - uses: actions/checkout@v4
          with:
            fetch-depth: 0

        - name: Setup Python 3.11
          uses: actions/setup-python@v5
          with:
            python-version: '3.11'
            cache: 'pip'

        - name: Installer les dépendances
          run: |
            pip install -r requirements.txt
            pip install -r requirements-dev.txt

        - name: Créer le fichier .env de test
          run: |
            cat > .env << EOF
            FLASK_ENV=testing
            TESTING=true
            SECRET_KEY=ci-test-secret-key
            DATABASE_URL=postgresql://test_user:test_password@localhost:5432/taskmanager_test
            REDIS_URL=redis://localhost:6379/0
            LOGSTASH_HOST=127.0.0.1
            LOGSTASH_PORT=1
            EOF

        - name: Lancer les tests avec coverage
          run: |
            pytest tests/ \
              --cov=app \
              --cov-report=xml \
              --cov-report=term-missing \
              --cov-report=html \
              --junit-xml=test-results.xml \
              -v

        - name: Publier les résultats de tests
          uses: dorny/test-reporter@v1
          if: always()
          with:
            name: Pytest Results
            path: test-results.xml
            reporter: java-junit

        - name: Uploader le rapport de coverage
          uses: actions/upload-artifact@v3
          with:
            name: coverage-report
            path: |
              coverage.xml
              htmlcov/

        - name: Coverage badge
          uses: codecov/codecov-action@v3
          with:
            file: coverage.xml
            token: ${{ secrets.CODECOV_TOKEN }}
            fail_ci_if_error: false

  WORKFLOW DE VÉRIFICATION DE SÉCURITÉ (.github/workflows/security.yml):
  ──────────────────────────────────────────────────────────────────────────
  nano .github/workflows/security.yml

  Contenu:
  name: Security Scan

  on:
    push:
      branches: [main, develop]
    schedule:
      - cron: '0 2 * * 1'   <- Tous les lundis à 2h du matin

  jobs:
    security-scan:
      name: Analyse de sécurité
      runs-on: ubuntu-latest
      steps:
        - uses: actions/checkout@v4

        - name: Setup Python
          uses: actions/setup-python@v5
          with:
            python-version: '3.11'

        - name: Installer bandit (analyse sécurité Python)
          run: pip install bandit safety

        - name: Analyse avec Bandit
          run: |
            bandit -r app/ \
              -f json \
              -o bandit-report.json \
              --severity-level medium \
              || true

        - name: Vérifier les dépendances vulnérables avec Safety
          run: |
            safety check \
              -r requirements.txt \
              --json \
              -o safety-report.json \
              || true

        - name: Uploader les rapports de sécurité
          uses: actions/upload-artifact@v3
          with:
            name: security-reports
            path: |
              bandit-report.json
              safety-report.json

  WORKFLOW DE NOTIFICATION (.github/workflows/notify.yml):
  ──────────────────────────────────────────────────────────
  nano .github/workflows/notify.yml

  Contenu:
  name: Notifications

  on:
    workflow_run:
      workflows: [CI - Tests, Lint, Coverage]
      types: [completed]

  jobs:
    notify-on-failure:
      name: Notifier l'équipe si échec
      runs-on: ubuntu-latest
      if: ${{ github.event.workflow_run.conclusion == 'failure' }}

      steps:
        - name: Envoyer notification Slack
          uses: 8398a7/action-slack@v3
          with:
            status: failure
            text: |
              [X] CI échoué sur *${{ github.event.workflow_run.head_branch }}*
              Auteur: ${{ github.event.workflow_run.triggering_actor.login }}
              Commit: ${{ github.event.workflow_run.head_sha }}
              Voir: ${{ github.event.workflow_run.html_url }}
          env:
            SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 7.2 - Réutiliser des workflows avec les actions composites
────────────────────────────────────────────────────────────────────────────────

  # Créer une action composite réutilisable pour le setup Python:
  mkdir -p .github/actions/setup-python-project

  nano .github/actions/setup-python-project/action.yml

  Contenu:
  name: Setup Python Project
  description: Setup Python avec cache et installation des dépendances
  inputs:
    python-version:
      description: Python version
      required: false
      default: '3.11'
    install-dev:
      description: Installer les dépendances de dev?
      required: false
      default: 'true'
  runs:
    using: composite
    steps:
      - name: Setup Python ${{ inputs.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ inputs.python-version }}
          cache: 'pip'

      - name: Installer requirements.txt
        shell: bash
        run: pip install -r requirements.txt

      - name: Installer requirements-dev.txt
        if: ${{ inputs.install-dev == 'true' }}
        shell: bash
        run: pip install -r requirements-dev.txt

  # Utilisation dans un workflow:
  - name: Setup Python
    uses: ./.github/actions/setup-python-project
    with:
      python-version: '3.11'
      install-dev: 'true'

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 7.3 - GitHub Environments: déploiements sécurisés
────────────────────────────────────────────────────────────────────────────────

INTERFACE WEB GitHub: Settings -> Environments -> New environment

  ENVIRONMENT "staging":
  ───────────────────────
  Name: staging
  Protection rules:
  [x] Required reviewers: @alice-dev (1 reviewer minimum)
  [x] Wait timer: 0 minutes
  Deployment branches: develop, release/**

  Environment secrets:
  STAGING_SERVER_HOST = 10.0.0.2
  STAGING_DB_PASSWORD = staging-secret-db-password

  ENVIRONMENT "production":
  ──────────────────────────
  Name: production
  Protection rules:
  [x] Required reviewers: @alice-dev, @bob-dev (les 2 doivent approuver!)
  [x] Wait timer: 5 minutes (délai de réflexion avant déploiement)
  Deployment branches: main (seulement main peut déployer en prod!)

  Environment secrets:
  PROD_SERVER_HOST    = 10.0.0.1
  PROD_DB_PASSWORD    = prod-ultra-secret-password
  PROD_SECRET_KEY     = prod-flask-secret-key

  Dans le workflow, utiliser:
  ────────────────────────────
  deploy-production:
    environment:
      name: production
      url: https://api.taskmanager.equipe.com
    # -> GitHub demandera approbation d'Alice ET Bob avant de continuer!


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 8 - GIT LARGE FILE STORAGE (GIT LFS)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

L'équipe doit versionner des fichiers de données volumineuses (datasets de test,
migrations SQL complexes, fichiers de configuration binaires).

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 8.1 - Installation et configuration de Git LFS
────────────────────────────────────────────────────────────────────────────────

  INSTALLATION:
  ─────────────
  # Mac:
  brew install git-lfs

  # Ubuntu/Debian:
  sudo apt-get install git-lfs

  # Windows: inclus dans Git for Windows récent

  # Initialiser LFS (une fois par machine):
  git lfs install
  # -> Git LFS initialized.

  CONFIGURER LES TYPES DE FICHIERS À GÉRER VIA LFS:
  ────────────────────────────────────────────────────
  # Définir quels fichiers vont dans LFS:
  git lfs track "*.db"              <- Bases de données SQLite de test
  git lfs track "*.sql.gz"          <- Dumps SQL compressés
  git lfs track "tests/fixtures/*.json.gz"  <- Gros fichiers de fixtures
  git lfs track "*.pdf"
  git lfs track "*.xlsx"

  # Cela met à jour .gitattributes:
  cat .gitattributes
  # -> *.db filter=lfs diff=lfs merge=lfs -text
  # -> *.sql.gz filter=lfs diff=lfs merge=lfs -text
  # -> tests/fixtures/*.json.gz filter=lfs diff=lfs merge=lfs -text

  git add .gitattributes
  git commit -m "chore: configure Git LFS for large test fixtures"

  VÉRIFIER QUE LFS FONCTIONNE:
  ─────────────────────────────
  # Ajouter un gros fichier:
  cp /chemin/vers/test_data.db tests/fixtures/
  git add tests/fixtures/test_data.db
  git status
  # -> new file: tests/fixtures/test_data.db

  git lfs status
  # -> On branch develop
  # -> Objects to be pushed to origin/develop:
  # ->   LFS tests/fixtures/test_data.db (5.3 MB)

  git commit -m "test: add SQLite test database fixture"
  git push

  # Le fichier .db est stocké sur le serveur LFS de GitHub
  # Dans le dépôt Git, seul un "pointeur" de quelques octets est stocké:
  cat tests/fixtures/test_data.db
  # -> version https://git-lfs.github.com/spec/v1
  # -> oid sha256:abc123def456...
  # -> size 5570560

  # Voir les fichiers trackés par LFS:
  git lfs ls-files

  # Migrer des fichiers déjà dans Git vers LFS:
  git lfs migrate import --include="*.db" --everything


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 9 - SCENARIOS AVANCÉS DE L'ÉQUIPE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

════════════════════════════════════════════════════════════════════════════════
SCÉNARIO D - Claire utilise git worktree pour travailler sur deux branches
════════════════════════════════════════════════════════════════════════════════

Claire doit simultanément continuer sa feature ET corriger un bug urgent
sans avoir à gérer les stashs ou changer de branche dans le même dossier.

CLAIRE dans son terminal:

  # Voir les worktrees existants:
  git worktree list
  # -> /home/claire/taskmanager  abc123d  [feature/7-task-tags]

  # Créer un nouveau worktree pour le hotfix:
  git worktree add ../taskmanager-hotfix hotfix/11-fix-redis-timeout
  # -> Preparing worktree (new branch 'hotfix/11-fix-redis-timeout')
  # -> HEAD is now at abc123d feat: initial project setup

  # Maintenant Claire a DEUX dossiers de travail sur le MÊME dépôt:
  ls ~/
  # -> taskmanager/          <- feature/7-task-tags en cours
  # -> taskmanager-hotfix/   <- hotfix/11 en cours

  # Elle peut travailler dans les deux en même temps (deux terminaux)!
  cd ~/taskmanager-hotfix
  # [corriger le bug Redis...]
  git add app/utils.py
  git commit -m "fix(redis): increase connection timeout to 30s"
  git push -u origin hotfix/11-fix-redis-timeout

  # Supprimer le worktree quand on a fini:
  git worktree remove ../taskmanager-hotfix
  git branch -d hotfix/11-fix-redis-timeout

════════════════════════════════════════════════════════════════════════════════
SCÉNARIO E - Bob recherche dans l'historique Git
════════════════════════════════════════════════════════════════════════════════

Bob cherche un morceau de code qui a été supprimé il y a quelques semaines.

BOB dans son terminal:

  # Chercher dans tous les commits le texte "def validate_priority":
  git log -S "def validate_priority" --all --oneline
  # -> c3d4e5f feat(models): add priority validation
  # -> d4e5f6g refactor(models): extract validation to utils

  # Voir le contenu de ce commit:
  git show c3d4e5f

  # Chercher par expression régulière:
  git log -G "def validate_[a-z]+" --all --oneline -p

  # Chercher dans le message des commits:
  git log --grep="priority" --oneline
  git log --grep="priority" --grep="validation" --all-match --oneline
  # --all-match: les DEUX expressions doivent être présentes

  # Chercher dans tous les fichiers à un commit donné:
  git grep "def validate_priority" c3d4e5f
  # -> c3d4e5f:app/models.py:    def validate_priority(self):

  # Chercher dans le code actuel:
  git grep "def validate"
  git grep -n "import redis"    <- Avec numéros de ligne
  git grep -l "redis"           <- Seulement les noms de fichiers

  # Chercher qui a modifié une ligne spécifique:
  git blame app/routes.py                    <- Auteur de chaque ligne
  git blame -L 45,60 app/routes.py           <- Seulement les lignes 45-60
  git blame -C app/routes.py                 <- Détecter les copier-collé

  SORTIE DE git blame app/routes.py:
  ───────────────────────────────────
  a3f8c1d (Alice Martin  2024-01-10 09:00) @tasks_bp.route('/tasks')
  b7d2e4f (Bob Dupont    2024-01-12 14:30) def get_tasks():
  c3d4e5f (Bob Dupont    2024-01-12 14:30)     query = Task.query
  d4e5f6g (Claire Leblanc 2024-01-15 11:00)    # Filtre par tags

  # Voir l'historique complet d'un fichier:
  git log --follow -p app/routes.py   <- --follow: suit les renommages

════════════════════════════════════════════════════════════════════════════════
SCÉNARIO F - Alice nettoie le dépôt et gère l'espace disque
════════════════════════════════════════════════════════════════════════════════

ALICE dans son terminal:

  # Voir la taille du dépôt .git/:
  du -sh .git/
  # -> 48M   .git/

  # Voir les gros objets dans l'historique:
  git rev-list --objects --all | \
    git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \
    sed -n 's/^blob //p' | \
    sort --numeric-sort --key=2 --reverse | \
    head -10

  # Compacter le dépôt (git garbage collection):
  git gc
  git gc --aggressive   <- Plus lent mais plus efficace

  # Élaguer les refs distantes obsolètes:
  git remote prune origin
  git fetch --prune

  # Supprimer les branches locales qui ont été mergées:
  git branch --merged develop | grep -v "develop\|main" | xargs git branch -d

  # Vérifier l'intégrité du dépôt:
  git fsck
  git fsck --full   <- Vérification complète

  SORTIE:
  ───────
  Checking object connectivity done.

  # Supprimer définitivement un fichier de TOUT l'historique:
  # (Utile si un secret a été commité par erreur)
  # ATTENTION: réécriture complète de l'historique!
  # Prévenir toute l'équipe avant! Ils devront re-cloner!

  git filter-branch --force --index-filter \
    'git rm --cached --ignore-unmatch .env.secret' \
    --prune-empty --tag-name-filter cat -- --all

  # Méthode moderne avec git-filter-repo (recommandée):
  # pip install git-filter-repo
  git filter-repo --path .env.secret --invert-paths

  # Forcer le push de toutes les branches réécrites:
  git push origin --force --all
  git push origin --force --tags

  # IMPORTANT: Invalider le token/secret compromis immédiatement sur la plateforme!
  # L'historique GitHub peut être mis en cache par des robots avant suppression.


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 10 - GITHUB CLI (gh): GitHub en ligne de commande
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

GitHub CLI permet de faire tout ce qu'on fait sur l'interface web depuis
le terminal. C'est particulièrement utile pour Bob et Claire.

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 10.1 - Installation et authentification
────────────────────────────────────────────────────────────────────────────────

  INSTALLATION:
  ─────────────
  # Mac:
  brew install gh

  # Ubuntu/Debian:
  sudo apt-get install gh

  # Windows: winget install --id GitHub.cli

  AUTHENTIFICATION:
  ──────────────────
  gh auth login

  SORTIE INTERACTIVE:
  ────────────────────
  ? What account do you want to log into? GitHub.com
  ? What is your preferred protocol for Git operations? SSH
  ? Upload your SSH public key to your GitHub account? ~/.ssh/id_ed25519_github.pub
  ? Title for your SSH key: MacBook Pro Bob 2024
  ? How would you like to authenticate GitHub CLI? Login with a web browser

  ! First copy your one-time code: ABCD-1234
  Press Enter to open github.com in your browser...
  [OK] Authentication complete.
  [OK] Configured git protocol
  [OK] Uploaded the SSH key to your GitHub account: MacBook Pro Bob 2024

  # Vérifier:
  gh auth status

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 10.2 - Commandes gh essentielles
────────────────────────────────────────────────────────────────────────────────

  GESTION DES PULL REQUESTS:
  ───────────────────────────
  # Créer une PR directement depuis le terminal:
  gh pr create \
    --base develop \
    --title "feat(routes): add task filtering" \
    --body "Closes #5" \
    --reviewer alice-dev,claire-dev \
    --assignee bob-dev \
    --label "enhancement,flask"

  # Lister les PRs:
  gh pr list
  gh pr list --state open
  gh pr list --assignee bob-dev
  gh pr list --label "enhancement"

  SORTIE:
  ───────
  #6  feat(routes): add task filtering  feature/5-task-filtering  OPEN
  #7  feat(routes): add task priority   feature/6-task-priority   OPEN

  # Voir les détails d'une PR:
  gh pr view 6
  gh pr view 6 --web    <- Ouvrir dans le navigateur

  # Se checkout sur la branche d'une PR (pour tester localement):
  gh pr checkout 6
  # -> Télécharge la branche et se place dessus!

  # Approuver une PR:
  gh pr review 6 --approve --body "LGTM! Great implementation!"

  # Demander des changements:
  gh pr review 6 --request-changes --body "Please add input validation"

  # Merger une PR:
  gh pr merge 6 --squash --delete-branch --body "feat(routes): add task filtering"

  # Vérifier le statut des checks d'une PR:
  gh pr checks 6

  SORTIE:
  ───────
  ci/tests     [OK]  Pass  45s  https://github.com/...
  ci/sonarqube [OK]  Pass  38s  https://github.com/...
  ci/docker    [OK]  Pass  1m   https://github.com/...

  GESTION DES ISSUES:
  ────────────────────
  # Créer une issue:
  gh issue create \
    --title "[BUG] POST /tasks crashes with 500" \
    --body "Description du bug..." \
    --label "bug,priority: high" \
    --assignee alice-dev \
    --milestone v1.0.1

  # Lister les issues:
  gh issue list
  gh issue list --label "bug"
  gh issue list --assignee bob-dev --state open

  # Voir une issue:
  gh issue view 8
  gh issue view 8 --web

  # Fermer une issue:
  gh issue close 8 --comment "Fixed in PR #9"

  # Rouvrir une issue:
  gh issue reopen 8

  GESTION DES RELEASES:
  ──────────────────────
  # Créer une release:
  gh release create v1.0.0 \
    --title "v1.0.0 - MVP" \
    --notes "Première release stable" \
    --target main

  # Lister les releases:
  gh release list

  # Télécharger les assets d'une release:
  gh release download v1.0.0

  GESTION DES WORKFLOWS (ACTIONS):
  ──────────────────────────────────
  # Voir les workflows:
  gh workflow list

  # Déclencher un workflow manuellement:
  gh workflow run ci.yml

  # Voir les runs d'un workflow:
  gh run list --workflow=ci.yml

  # Voir les logs d'un run:
  gh run view 12345678
  gh run view 12345678 --log

  # Annuler un run en cours:
  gh run cancel 12345678

  GESTION DU DÉPÔT:
  ──────────────────
  # Cloner un dépôt avec gh:
  gh repo clone equipe/taskmanager

  # Créer un dépôt:
  gh repo create equipe/new-project --private --description "Nouveau projet"

  # Voir les informations du dépôt:
  gh repo view
  gh repo view equipe/taskmanager

  # Lister les collaborateurs:
  gh api repos/equipe/taskmanager/collaborators

  # Créer un fork:
  gh repo fork equipe/taskmanager --clone

  ALIAS GH PRATIQUES:
  ────────────────────
  gh alias set prc 'pr create --fill'
  gh alias set prv 'pr view --web'
  gh alias set prm 'pr merge --squash --delete-branch'

  # Utilisation:
  gh prc     <- Crée une PR avec le titre/body du dernier commit
  gh prv 6   <- Ouvre la PR #6 dans le navigateur
  gh prm 6   <- Merge la PR #6


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 11 - BONNES PRATIQUES GIT DE L'ÉQUIPE (RÈGLES D'OR)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

────────────────────────────────────────────────────────────────────────────────
RÈGLE 1 - Commits atomiques et messages de qualité
────────────────────────────────────────────────────────────────────────────────

  # [X] MAUVAIS commit:
  git commit -m "modifs"
  git commit -m "fix"
  git commit -m "wip"
  git commit -m "corrections de Bob"
  git commit -m "ça marche maintenant"

  # [OK] BON commit:
  git commit -m "feat(routes): add rate limiting per IP address

  Implement Flask-Limiter to protect the API from abuse:
  - 100 requests/minute per IP for most endpoints
  - 10 requests/minute for authentication endpoints
  - Custom error message returned with 429 status
  - Whitelist for internal monitoring IPs

  Configuration via environment variables:
  - RATE_LIMIT_ENABLED: true/false (default: true)
  - RATE_LIMIT_DEFAULT: X per minute (default: 100)

  Closes #15"

  # UN commit = UNE modification logique
  # Un reviewer doit comprendre POURQUOI en lisant seulement le message

────────────────────────────────────────────────────────────────────────────────
RÈGLE 2 - Ne jamais forcer le push sur les branches partagées
────────────────────────────────────────────────────────────────────────────────

  # [X] JAMAIS sur main ou develop:
  git push --force origin main      <- Peut effacer le travail des autres!
  git push --force origin develop

  # [OK] Sur une branche feature perso (après rebase):
  git push --force-with-lease origin feature/5-task-filtering
  # --force-with-lease vérifie qu'on n'écrase pas un push récent d'autrui

  # [OK] Prévenir l'équipe si push forcé nécessaire sur une branche partagée:
  # 1. En informer tout le monde sur Slack
  # 2. S'assurer que personne ne travaille dessus
  # 3. Faire le push forcé
  # 4. Tout le monde doit faire: git fetch --all && git reset --hard origin/branche

────────────────────────────────────────────────────────────────────────────────
RÈGLE 3 - Toujours tirer (pull) avant de pousser (push)
────────────────────────────────────────────────────────────────────────────────

  # Workflow de début de journée:
  git checkout develop
  git pull origin develop          <- TOUJOURS mettre à jour avant de travailler
  git checkout feature/ma-feature
  git rebase origin/develop        <- Rebaser sur develop à jour

  # Workflow de fin de journée/avant push:
  git fetch origin                 <- Voir ce qui a changé sur le remote
  git status                       <- Vérifier l'état local
  git push origin feature/ma-feature

  # Si rejeté ("rejected - non fast-forward"):
  # Quelqu'un a poussé sur cette branche entre temps
  git pull --rebase origin feature/ma-feature  <- Intégrer les changements
  git push origin feature/ma-feature

────────────────────────────────────────────────────────────────────────────────
RÈGLE 4 - Revue de code obligatoire avant merge dans develop
────────────────────────────────────────────────────────────────────────────────

  CHECKLIST DE CODE REVIEW pour Alice, Bob et Claire:
  ─────────────────────────────────────────────────────

  Code:
  [x] Le code fait exactement ce que le titre de la PR décrit
  [x] Pas de code mort ou commenté inutilement
  [x] Noms de variables/fonctions explicites
  [x] Pas de secrets hardcodés (passwords, API keys)
  [x] Gestion des erreurs (try/except, validation des inputs)
  [x] Pas de N+1 queries (requêtes SQL en boucle)
  [x] Pas de copier-coller évitable (factoriser)

  Tests:
  [x] Tous les cas nominaux sont testés
  [x] Les cas d'erreur sont testés (400, 404, 500)
  [x] Les cas limites sont testés (liste vide, valeurs nulles)
  [x] Coverage ne régresse pas

  Sécurité:
  [x] Pas d'injection SQL possible
  [x] Validation des inputs utilisateur
  [x] Pas de données sensibles dans les logs
  [x] Permissions vérifiées si applicable

  Documentation:
  [x] Docstrings pour les fonctions publiques
  [x] CHANGELOG.md mis à jour si feature importante
  [x] README mis à jour si comportement change

────────────────────────────────────────────────────────────────────────────────
RÈGLE 5 - Utiliser git fetch + git log avant git pull
────────────────────────────────────────────────────────────────────────────────

  # Au lieu de git pull aveuglément:

  # 1. Voir ce qui a changé sur le remote:
  git fetch origin

  # 2. Voir les commits qui seront intégrés:
  git log HEAD..origin/develop --oneline
  # -> a1b2c3d feat(routes): add priority filter (Bob)
  # -> d4e5f6g fix(models): handle null tags (Claire)

  # 3. Maintenant décider d'intégrer:
  git pull origin develop          <- Merge (crée un merge commit)
  # ou:
  git rebase origin/develop        <- Rebase (historique linéaire)

────────────────────────────────────────────────────────────────────────────────
RÈGLE 6 - Maintenir un .gitignore à jour
────────────────────────────────────────────────────────────────────────────────

  # Ajouter au .gitignore si un nouveau type de fichier doit être ignoré:
  echo "*.pyc" >> .gitignore
  git add .gitignore
  git commit -m "chore: update .gitignore for Python bytecode"

  # Template .gitignore pour Python sur gitignore.io:
  curl https://www.toptal.com/developers/gitignore/api/python,flask,venv,macos,linux,windows,visualstudiocode,jetbrains

  # Si un fichier est déjà tracké et doit être ignoré:
  git rm --cached chemin/vers/fichier.pyc
  echo "*.pyc" >> .gitignore
  git add .gitignore
  git commit -m "chore: untrack .pyc files and add to .gitignore"

────────────────────────────────────────────────────────────────────────────────
RÈGLE 7 - Documentation de l'historique avec CHANGELOG.md
────────────────────────────────────────────────────────────────────────────────

  Alice maintient CHANGELOG.md au format Keep a Changelog:

  nano CHANGELOG.md

  Contenu:
  # Changelog

  Tous les changements notables de ce projet sont documentés ici.
  Format: [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
  Versionning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html)

  ## [Unreleased]
  ### Added
  - Système d'authentification JWT (en cours - #9)

  ## [1.0.1] - 2024-01-20
  ### Fixed
  - Validation du type de description dans POST /tasks (#8)

  ## [1.0.0] - 2024-01-15
  ### Added
  - CRUD complet pour les tâches
  - Filtrage par statut, dates, mots-clés (#5)
  - Filtrage par priorité (#6)
  - Filtrage par tags (#7)
  - Pagination des résultats
  - Endpoint /health pour monitoring

  ### Changed
  - GET /tasks retourne maintenant un objet paginé (BREAKING CHANGE)

  [Unreleased]: https://github.com/equipe/taskmanager/compare/v1.0.1...HEAD
  [1.0.1]: https://github.com/equipe/taskmanager/compare/v1.0.0...v1.0.1
  [1.0.0]: https://github.com/equipe/taskmanager/releases/tag/v1.0.0

  # Générer automatiquement le changelog depuis les commits:
  # pip install commitizen
  cz changelog
  cz bump    <- Bump automatique de version selon les commits


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 12 - RÉSUMÉ VISUEL - QUI FAIT QUOI ET QUAND (GIT/GITHUB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ALICE (Lead / Responsable Git/GitHub)
  ───────────────────────────────────────
  [OK] Configure le dépôt GitHub (settings, branches protégées, CODEOWNERS)
  [OK] Définit la stratégie de branches (Git Flow)
  [OK] Maintient les templates PR et Issues
  [OK] Gère les labels et milestones
  [OK] Configure GitHub Actions (CI/CD)
  [OK] Configure Dependabot et la sécurité
  [OK] Merge les PRs dans main (gate keeper)
  [OK] Crée les tags et releases
  [OK] Maintient CHANGELOG.md et le Wiki
  [OK] Gère les incidents (hotfix, rollback)
  [OK] Prend les décisions d'architecture (Discussions)

  BOB et CLAIRE (Développeurs)
  ──────────────────────────────
  [OK] Configurent Git et SSH une seule fois
  [OK] Travaillent sur des branches feature/fix dédiées
  [OK] Suivent la convention Conventional Commits
  [OK] Créent des issues avant de coder
  [OK] Ouvrent des PRs avec description complète
  [OK] Répondent aux commentaires de review
  [OK] Font des code reviews pour les PRs des autres
  [OK] Utilisent git stash pour les interruptions
  [OK] Rebasent sur develop avant une PR
  [OK] Nettoient leurs branches locales après merge

  WORKFLOW COMPLET D'UNE FEATURE GIT/GITHUB:
  ────────────────────────────────────────────

  Jour 1                  Jour 2-5               Jour 5-6
  ──────                  ────────               ────────
  Créer Issue GitHub      Commits atomiques       Push final
  v                       par petites             v
  git checkout develop    unités logiques         PR sur GitHub
  git pull                v                       v
  v                       git add -p              Code review
  git checkout -b         git commit -m           (Alice + Claire)
  feature/N-nom           "type(scope): msg"      v
  v                       v                       Corrections si demandées
  git push -u origin      git push                v
  feature/N-nom           (pas obligatoire        Squash & Merge
                          chaque jour)            v
                                                  Issue fermée auto
                                                  v
                                                  Nettoyage branche

  ARCHITECTURE GIT DE L'ÉQUIPE:
  ──────────────────────────────

  main (production)
  │  ^ PR + Review + Tests [OK] + Approbations
  │
  develop (intégration)
  │  ^ PR + Review + Tests [OK]
  │
  ├── feature/5-task-filtering (Bob)
  ├── feature/7-task-tags (Claire)
  ├── fix/10-redis-connection (Bob)
  └── hotfix/11-urgent-fix (Alice) ─── merge directement dans main + develop

  GITHUB: ANATOMIE D'UN DÉPÔT BIEN GÉRÉ:
  ─────────────────────────────────────────

  github.com/equipe/taskmanager
  ├── [DOSSIER] Code              <- Code source + historique Git
  ├── [BUG] Issues (12)       <- Bugs + features organisés par labels/milestones
  ├── [MELANGE] Pull Requests (2) <- Code en attente de review
  ├── [CONFIG] Actions           <- CI/CD automatisé
  ├── [VERROUILLE] Security          <- Dependabot + CodeQL + Secret scanning
  ├── [GRAPHIQUE] Insights          <- Métriques et statistiques de l'équipe
  ├── [LISTE] Projects          <- Kanban: Backlog -> To Do -> In Progress -> Done
  ├── [SPEECH_BALLOON] Discussions       <- Architecture, Q&A, Annonces
  ├── [GUIDE] Wiki              <- Documentation technique
  └── [PACKAGE] Packages          <- Images Docker publiées (ghcr.io)


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
PARTIE 13 - CHEATSHEET GIT/GITHUB (RÉFÉRENCE RAPIDE ÉQUIPE)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  CONFIGURATION (une fois par machine):
  ──────────────────────────────────────
  git config --global user.name "Prénom Nom"
  git config --global user.email "email@equipe.com"
  git config --global pull.rebase true
  git config --global init.defaultBranch main
  git config --list

  DÉMARRAGE:
  ──────────
  git init                            # Initialiser un nouveau dépôt
  git clone git@github.com:...        # Cloner un dépôt existant
  git clone --recursive ...           # Cloner avec les submodules

  ÉTAT ET DIFFÉRENCES:
  ─────────────────────
  git status                          # État du working directory
  git status -s                       # Format court
  git diff                            # Modifications non stagées
  git diff --staged                   # Modifications stagées
  git diff main..develop              # Différence entre deux branches

  STAGE ET COMMIT:
  ─────────────────
  git add fichier.py                  # Stager un fichier
  git add .                           # Stager tout
  git add -p                          # Stager interactivement (par chunks)
  git restore --staged fichier.py     # Déstaguer un fichier
  git commit -m "type(scope): msg"    # Committer
  git commit --amend                  # Modifier le DERNIER commit (local!)
  git commit --no-verify              # Bypass les hooks

  HISTORIQUE:
  ────────────
  git log --oneline --graph --all     # Historique visuel complet
  git log --author="Bob"              # Filtrer par auteur
  git log --grep="feat:"              # Filtrer par message
  git log -S "texte"                  # Commits qui touchent ce texte
  git log -- app/routes.py            # Historique d'un fichier
  git show a3f8c1d                    # Détails d'un commit
  git blame app/routes.py             # Auteur de chaque ligne

  BRANCHES:
  ──────────
  git branch                          # Lister les branches locales
  git branch -a                       # Toutes les branches
  git checkout -b feature/nom         # Créer et aller sur une branche
  git switch -c feature/nom           # Idem (syntaxe moderne)
  git checkout develop                # Aller sur develop
  git branch -d feature/nom           # Supprimer une branche locale
  git push origin --delete feature/nom # Supprimer sur remote

  SYNCHRONISATION:
  ─────────────────
  git fetch origin                    # Récupérer sans merger
  git fetch --prune                   # Récupérer et supprimer les refs obsolètes
  git pull origin develop             # Fetch + merge
  git pull --rebase origin develop    # Fetch + rebase
  git push origin feature/nom         # Pousser une branche
  git push -u origin feature/nom      # Pousser et lier au remote
  git push --force-with-lease ...     # Push forcé sécurisé

  REBASE ET MERGE:
  ─────────────────
  git rebase origin/develop           # Rebaser sur develop
  git rebase -i HEAD~3                # Rebase interactif (3 derniers commits)
  git merge --no-ff feature/nom       # Merge avec commit de merge
  git merge --squash feature/nom      # Merge en écrasant en 1 commit

  ANNULATION:
  ────────────
  git restore fichier.py              # Annuler modifs d'un fichier (discard)
  git restore --staged fichier.py     # Déstaguer
  git revert a3f8c1d                  # Créer un commit inversant a3f8c1d
  git reset --soft HEAD~1             # Défaire le commit, garder en stage
  git reset --mixed HEAD~1            # Défaire le commit, garder non stagé
  git reset --hard HEAD~1             # Défaire le commit ET les modifs (DANGER!)
  git rebase --abort                  # Annuler un rebase en cours
  git merge --abort                   # Annuler un merge en cours

  STASH:
  ───────
  git stash save "description"        # Sauvegarder les modifs en cours
  git stash list                      # Lister les stashs
  git stash pop                       # Restaurer et supprimer le dernier stash
  git stash apply stash@{0}           # Restaurer sans supprimer
  git stash drop stash@{0}            # Supprimer un stash
  git stash clear                     # Supprimer tous les stashs

  TAGS:
  ──────
  git tag -a v1.0.0 -m "Release v1.0.0"   # Créer un tag annoté
  git tag                                   # Lister les tags
  git push origin v1.0.0                   # Pousser un tag
  git push origin --tags                   # Pousser tous les tags
  git tag -d v1.0.0                        # Supprimer un tag local
  git push origin --delete v1.0.0          # Supprimer un tag remote

  AVANCÉ:
  ────────
  git cherry-pick a3f8c1d             # Appliquer un commit sur la branche courante
  git bisect start/good/bad           # Trouver le commit responsable d'un bug
  git reflog                          # Historique de tous les mouvements de HEAD
  git grep "texte"                    # Chercher dans le code actuel
  git worktree add ../autre develop   # Dossier de travail sur une autre branche
  git submodule add <url> lib/        # Ajouter un sous-dépôt

  NETTOYAGE:
  ───────────
  git clean -fd                       # Supprimer les fichiers non trackés
  git clean -fdx                      # Idem + les fichiers ignorés par .gitignore
  git gc                              # Compacter le dépôt
  git remote prune origin             # Supprimer les refs obsolètes
  git branch --merged | grep -v main  # Branches déjà mergées (à supprimer)

  GITHUB CLI (gh):
  ─────────────────
  gh pr create --base develop         # Créer une PR
  gh pr list                          # Lister les PRs
  gh pr checkout 6                    # Checkout la branche d'une PR
  gh pr review 6 --approve            # Approuver une PR
  gh pr merge 6 --squash              # Merger une PR
  gh issue create --title "..."       # Créer une issue
  gh issue list --label "bug"         # Lister les issues
  gh run list --workflow=ci.yml       # Lister les runs CI/CD
  gh release create v1.0.0           # Créer une release

  ALIAS PRATIQUES (~/.gitconfig ou ~/.bashrc):
  ─────────────────────────────────────────────
  # Dans .gitconfig [alias]:
  st = status
  co = checkout
  br = branch
  lg = log --oneline --graph --decorate --all
  last = log -1 HEAD
  unstage = reset HEAD --
  wip = commit -am 'WIP: work in progress'
  plog = log --pretty=format:'%h %ad | %s%d [%an]' --graph --date=short
  undo = reset HEAD~1 --mixed
  up = pull --rebase origin

  # Dans .bashrc/.zshrc:
  alias gs='git status'
  alias ga='git add'
  alias gc='git commit -m'
  alias gp='git push'
  alias gpl='git pull --rebase'
  alias gco='git checkout'
  alias gbr='git branch'
  alias glg='git log --oneline --graph --all'
  alias gdf='git diff'
  alias gst='git stash'

  ERREURS FRÉQUENTES ET SOLUTIONS:
  ──────────────────────────────────

  "fatal: refusing to merge unrelated histories":
  -> git pull origin main --allow-unrelated-histories

  "error: failed to push some refs to ...":
  -> git pull --rebase origin branche && git push

  "CONFLICT (content): Merge conflict in ...":
  -> Ouvrir le fichier, résoudre manuellement, git add, git commit

  "Your local changes would be overwritten by merge":
  -> git stash && git pull && git stash pop

  "detached HEAD state":
  -> git checkout main  (ou le nom de la branche)
  -> Ou créer une branche: git checkout -b ma-branche

  "fatal: not a git repository":
  -> cd dans le bon dossier, ou git init si nouveau projet

  "Permission denied (publickey)":
  -> Vérifier la clé SSH: ssh -T git@github.com
  -> Recharger l'agent: eval "$(ssh-agent -s)" && ssh-add ~/.ssh/id_ed25519_github

  "error: src refspec main does not match any":
  -> Il n'y a pas encore de commits: git add . && git commit -m "initial"

================================================================================
FIN DE LA SECTION - GIT & GITHUB EN ÉQUIPE DE 3 DÉVELOPPEURS (FLASK/PYTHON)
================================================================================