Aller au contenu principal

TP6 — Git Avancé

Travaux Pratiques 60 min Module 05

Objectifs

À la fin de ce TP, vous aurez pratiqué :

  • Créer des tags annotés et des releases GitHub
  • Utiliser git cherry-pick pour des corrections ciblées
  • Utiliser git bisect pour trouver un bug
  • Créer des hooks Git fonctionnels
  • Utiliser git reflog pour récupérer du travail perdu

Mise en place

gh repo create git-tp6-avance \
--public \
--description "TP6 - Techniques Git Avancées" \
--clone
cd git-tp6-avance

Créez app.py :

"""Application de gestion de tâches."""

TACHES = []

def ajouter_tache(titre, priorite="normale"):
tache = {"id": len(TACHES) + 1, "titre": titre, "priorite": priorite, "terminee": False}
TACHES.append(tache)
return tache

def marquer_terminee(id_tache):
for tache in TACHES:
if tache["id"] == id_tache:
tache["terminee"] = True
return tache
raise ValueError(f"Tâche {id_tache} non trouvée")

def obtenir_statistiques():
total = len(TACHES)
terminees = sum(1 for t in TACHES if t["terminee"])
return {
"total": total,
"terminees": terminees,
"en_cours": total - terminees
}
git add .
git commit -m "feat: application de gestion de tâches initiale"
git push -u origin main

Partie 1 : Tags et Releases

Créer l'historique des versions

# Version 1.0.0
git tag -a v1.0.0 -m "Release v1.0.0 — Première version stable"
git push origin v1.0.0

# Ajouter une nouvelle fonctionnalité

Ajoutez à app.py :

def filtrer_taches(priorite=None, terminee=None):
"""Filtrer les tâches par priorité ou statut."""
resultats = TACHES.copy()
if priorite:
resultats = [t for t in resultats if t["priorite"] == priorite]
if terminee is not None:
resultats = [t for t in resultats if t["terminee"] == terminee]
return resultats
git add app.py
git commit -m "feat: ajouter le filtrage des tâches"
git push

# Version 1.1.0
git tag -a v1.1.0 -m "Release v1.1.0 — Filtrage des tâches"
git push origin v1.1.0

Créer des releases GitHub

# Créer la release v1.0.0
gh release create v1.0.0 \
--title "Version 1.0.0 — Première release stable" \
--notes "## Première release stable

### Fonctionnalités
- Ajouter des tâches avec priorité
- Marquer les tâches comme terminées
- Obtenir les statistiques de l'équipe"

# Créer la release v1.1.0
gh release create v1.1.0 \
--title "Version 1.1.0 — Filtrage avancé" \
--generate-notes

# Voir les releases
gh release list

Partie 2 : Cherry-pick

Scénario : Correction urgente sur la branche release

# Créer une branche de release
git switch -c release/v1.0
git push -u origin release/v1.0

# Simuler un développement sur main
git switch main

Ajoutez à app.py :

def rechercher_taches(query):
"""Rechercher des tâches par titre."""
return [t for t in TACHES if query.lower() in t["titre"].lower()]
git add app.py
git commit -m "feat: ajouter la recherche de tâches"

# Simuler une correction de bug critique
echo "# Correction critique validée" >> README.md
git add README.md
git commit -m "fix: corriger la gestion des tâches avec ID dupliqués"

# Récupérer le hash de la correction
git log --oneline

# Cherry-pick de la correction vers release/v1.0
HASH_FIX=$(git log --oneline | head -1 | awk '{print $1}')
git switch release/v1.0
git cherry-pick $HASH_FIX

# Vérifier que la correction est appliquée
git log --oneline
git switch main

Partie 3 : Git bisect

Créer un historique avec un bug introduit

git switch main

# Commit 1 : Fonctionnel
git commit --allow-empty -m "chore: version v1.2 - base saine"
git tag v1.2.0

# Commit 2 : Toujours fonctionnel
git commit --allow-empty -m "feat: amélioration mineure A"

# Commit 3 : Introduction du bug (dans app.py)

Modifiez obtenir_statistiques dans app.py pour introduire un bug :

def obtenir_statistiques():
total = len(TACHES)
terminees = sum(1 for t in TACHES if t["terminee"])
return {
"total": total,
"terminees": terminees,
"en_cours": total - terminees,
"pourcentage": terminees / total * 100 # BUG: division par zéro si TACHES est vide !
}
git add app.py
git commit -m "feat: ajouter le pourcentage aux statistiques"

# Commit 4 : Bug non détecté
git commit --allow-empty -m "feat: amélioration mineure B"

# Commit 5 : Bug toujours présent
git commit --allow-empty -m "docs: mise à jour de la documentation"
git push

Trouver le bug avec bisect

# Créer un script de test
cat > test_bisect.sh << 'EOF'
#!/bin/bash
python -c "
import sys
sys.path.insert(0, '.')
from app import obtenir_statistiques, TACHES
TACHES.clear()
try:
stats = obtenir_statistiques()
exit(0)
except (ZeroDivisionError, KeyError):
exit(1)
"
EOF
chmod +x test_bisect.sh

# Lancer bisect
git bisect start
git bisect bad HEAD
git bisect good v1.2.0
git bisect run ./test_bisect.sh

# Git trouve le commit coupable !
# Terminer bisect
git bisect reset

Partie 4 : Créer des hooks Git

mkdir -p .githooks

Créez .githooks/commit-msg :

#!/bin/bash
FICHIER_MSG="$1"
MESSAGE=$(cat "$FICHIER_MSG")

PATTERN="^(feat|fix|docs|style|refactor|test|chore|perf|ci|revert)(\(.+\))?: .+"

if ! echo "$MESSAGE" | grep -qE "$PATTERN"; then
echo "ERREUR : Message de commit invalide !"
echo "Format requis : type(portée): description"
echo "Types valides : feat, fix, docs, style, refactor, test, chore, perf, ci, revert"
echo "Votre message : $MESSAGE"
exit 1
fi

exit 0

Créez .githooks/pre-commit :

#!/bin/bash
echo "Vérification pre-commit..."

# Vérifier l'absence de fichiers .env
if git diff --cached --name-only | grep -E "\.env$"; then
echo "ERREUR : Fichier .env détecté !"
exit 1
fi

echo "Vérifications pre-commit réussies ✅"
exit 0
chmod +x .githooks/commit-msg .githooks/pre-commit

# Configurer Git pour utiliser ces hooks
git config core.hooksPath .githooks

# Tester le hook commit-msg
git add .githooks/
git commit -m "mauvais message sans format" # Devrait échouer

git commit -m "chore: ajouter les hooks Git de l'équipe" # Devrait réussir
git push

Partie 5 : git reflog — Récupérer du travail perdu

# Créer un commit à "perdre"
echo "# Travail important" > important.md
git add important.md
git commit -m "docs: ajouter documentation importante"

# "Accidentellement" perdre ce commit
git reset --hard HEAD~1

# Paniquer... puis utiliser reflog !
git reflog

# Trouver le commit perdu
HASH_PERDU=$(git reflog | grep "docs: ajouter documentation importante" | awk '{print $1}')
echo "Commit perdu retrouvé : $HASH_PERDU"

# Récupérer le commit perdu
git switch -c recuperation/$HASH_PERDU
git cherry-pick $HASH_PERDU

# Ou directement reset vers ce commit
# git reset --hard $HASH_PERDU

# Fusionner sur main
git switch main
git merge recuperation/$HASH_PERDU
git branch -d recuperation/$HASH_PERDU

# Vérifier que le fichier est récupéré
cat important.md
git push

Checklist de validation

  • Les tags v1.0.0 et v1.1.0 sont visibles sur GitHub avec leurs notes de release
  • Un cherry-pick a été effectué de main vers release/v1.0
  • git bisect a trouvé le commit qui a introduit le bug
  • Les hooks .githooks/pre-commit et .githooks/commit-msg fonctionnent
  • Un commit "perdu" a été récupéré avec git reflog

Résumé

Félicitations ! Vous avez maîtrisé les techniques Git avancées :

  • Versionnage avec tags SemVer et releases GitHub
  • Cherry-pick pour des corrections ciblées
  • Bisect pour debugger l'historique
  • Hooks pour automatiser la qualité du code
  • Reflog comme filet de sécurité ultime

Vous êtes maintenant un utilisateur Git avancé !