Git avancé : branches, Pull Requests et résolution de conflits de fusion

Travailler seul sur main fonctionne pour débuter, mais dès que vous développez plusieurs fonctionnalités en parallèle (ou que vous travaillez en équipe) les branches deviennent indispensables. Elles vous permettent d'isoler chaque développement dans sa propre ligne d'historique, de soumettre du code à relecture via une Pull Request, et de fusionner proprement dans la branche principale. Ce guide vous emmène de la création de votre première branche jusqu'à la résolution des conflits les plus complexes.
Sommaire
  1. Comprendre les branches - la métaphore de la photocopie
  2. Créer et naviguer entre les branches
  3. Le workflow Git Flow et Feature Branch
  4. Travailler en parallèle sur plusieurs fonctionnalités
  5. Pousser une branche et ouvrir une Pull Request sur GitHub
  6. Relire et merger une Pull Request
  7. Comprendre et résoudre les conflits de fusion
  8. Stratégies avancées : rebase, squash, stash
  9. Cheatsheet - toutes les commandes de branches

1. Comprendre les branches - la métaphore du cahier brouillon

Imaginez votre projet comme un document officiel. main est l'original (propre, stable, toujours fonctionnel). Créer une branche, c'est photocopier ce document sur un cahier brouillon : vous pouvez rayer, corriger, expérimenter librement. L'original reste intact. Quand vous êtes satisfait, vous recopiez vos modifications sur l'original : c'est la fusion (merge).

En pratique, une branche Git est simplement un pointeur vers un commit. Elle ne copie pas les fichiers (elle est légère, instantanée et ne coûte presque rien à créer).

main / master
La branche principale. Toujours stable, toujours déployable. On ne travaille jamais directement dessus en équipe.
feature branch
Une branche par fonctionnalité. Durée de vie courte (elle est supprimée après fusion). Ex : feat/page-contact.
hotfix branch
Correction urgente en production. Part de main, y retourne immédiatement après correction. Ex : fix/typo-accueil.

2. Créer et naviguer entre les branches

Voici les commandes fondamentales pour gérer les branches au quotidien :

# Voir toutes les branches (locale + distantes)
git branch -a

# Créer une nouvelle branche (sans basculer dessus)
git branch feat/page-contact

# Créer ET basculer sur la nouvelle branche (la façon la plus courante)
git checkout -b feat/page-contact

# Syntaxe moderne équivalente (Git 2.23+)
git switch -c feat/page-contact

# Basculer sur une branche existante
git checkout main
git switch main

# Voir sur quelle branche on est
git branch --show-current

# Supprimer une branche locale (après fusion)
git branch -d feat/page-contact

# Forcer la suppression (branche non mergée)
git branch -D feat/abandonnee
💡 Adoptez une convention de nommage cohérente pour vos branches. Le format type/description-courte est très répandu : feat/ pour les fonctionnalités, fix/ pour les corrections, docs/ pour la documentation, refactor/ pour les refactorisations. Les tirets remplacent les espaces, tout en minuscules.

Vérifier son état avant de changer de branche

Avant de faire git switch, assurez-vous que votre working directory est propre. Des modifications non committées peuvent être emportées d'une branche à l'autre, ce qui crée de la confusion :

git status     # doit afficher "nothing to commit, working tree clean"
git switch main

Si vous avez des modifications en cours non prêtes pour un commit, utilisez git stash pour les mettre de côté temporairement (voir section 8).

3. Le workflow Feature Branch - la règle d'or

Le Feature Branch Workflow est simple : une fonctionnalité = une branche. Vous ne commitez jamais directement sur main. Chaque modification passe par une branche dédiée et une Pull Request. Ce workflow fonctionne aussi bien seul qu'en équipe.

main stable
git switch -c feat/xxx
commits sur la branche
git push origin feat/xxx
Pull Request GitHub
merge dans main

Ce cycle garantit que main ne contient que du code relu et validé. Chaque branche représente une unité de travail isolée et traçable dans l'historique Git.

⚠ Ne créez jamais une branche qui dure des semaines sans être mergée. Plus une branche diverge de main, plus les conflits à la fusion seront nombreux et difficiles à résoudre. Visez des branches qui vivent 1 à 5 jours maximum.

4. Travailler en parallèle sur plusieurs fonctionnalités

Voici un scénario concret : vous développez simultanément la page de contact et le système d'authentification de votre blog Django. Ce sont deux fonctionnalités indépendantes (elles méritent deux branches distinctes).

Préparer les deux branches depuis main

# Partir d'un main à jour
git switch main
git pull

# Branche 1 : système de tags pour les articles
git switch -c feat/tags
# ... développez les modèles, vues, templates pour les tags ...
git add .
git commit -m "feat: ajout du modèle Tag avec slug auto"
git add .
git commit -m "feat: filtre des articles par tag dans les vues"

# Revenir sur main pour créer la 2ème branche
git switch main

# Branche 2 : authentification utilisateur
git switch -c feat/auth
# ... développez l'inscription, connexion, déconnexion ...
git add .
git commit -m "feat: formulaire d'inscription avec validation email"
git add .
git commit -m "feat: vue de connexion et gestion des sessions"

Basculer entre les branches en cours de travail

# Voir l'état de toutes vos branches
git branch -v
# * feat/auth         a3f9c12 feat: vue de connexion et gestion des sessions
#   feat/tags         7b2e891 feat: filtre des articles par tag dans les vues
#   main              f1d4a20 init: initialisation du projet Django

# Basculer sur feat/tags pour y continuer le travail
git switch feat/tags
# ... ajoutez un template pour la page de liste par tag ...
git commit -m "feat: template liste-par-tag.html avec pagination"

# Revenir sur feat/auth
git switch feat/auth
Les deux branches évoluent totalement indépendamment. Les modifications de feat/tags n'apparaissent pas dans feat/auth et vice-versa, exactement comme si deux développeurs travaillaient en parallèle sur deux copies du projet.

Garder sa branche à jour avec main

Si d'autres commits sont arrivés sur main pendant que vous travailliez sur votre branche (ex. un collègue a mergé une PR), mettez votre branche à jour pour éviter un conflit massif à la fusion :

# Depuis votre branche feat/auth
git switch feat/auth

# Option 1 : merge de main dans votre branche (crée un commit de merge)
git merge main

# Option 2 : rebase sur main (historique plus linéaire - voir section 8)
git rebase main

5. Pousser une branche et ouvrir une Pull Request sur GitHub

Une fois votre fonctionnalité prête, poussez la branche sur GitHub et ouvrez une Pull Request (PR) : c'est la demande formelle de fusionner votre travail dans main.

Pousser la branche sur GitHub

# Pousser la branche et la lier au dépôt distant (-u crée le tracking)
git push -u origin feat/tags

# Les pushes suivants sur cette branche se résument à :
git push

Ouvrir la Pull Request sur GitHub

1

Allez sur votre dépôt GitHub. Une bannière jaune apparaît : "feat/tags had recent pushes - Compare & pull request". Cliquez dessus.

2

Remplissez la Pull Request :

  • Titre : court et descriptif (ex. "Ajout du système de tags pour les articles")
  • Description : expliquez ce que fait la PR, pourquoi ces choix techniques, les tests effectués. Plus c'est détaillé, plus la relecture est rapide.
  • Reviewers : assignez un ou plusieurs relecteurs si vous travaillez en équipe
  • Labels : feature, bug, documentation… pour organiser les PRs
3

Cliquez sur "Create pull request". GitHub affiche automatiquement le diff de tous vos commits et vérifie s'il y a des conflits avec main.

💡 Une Pull Request n'est pas seulement un bouton "merger". C'est un espace de discussion, de relecture de code, et d'intégration continue. GitHub peut y déclencher des tests automatiques (CI/CD) et bloquer le merge tant que les tests ne passent pas ou que la PR n'a pas été approuvée.

Mettre à jour une PR après relecture

Un relecteur a laissé des commentaires sur votre PR ? Pas besoin d'en ouvrir une nouvelle. Commitez et poussez simplement sur la même branche (GitHub met la PR à jour automatiquement) :

# Après avoir pris en compte les retours
git add blog/templates/blog/liste-par-tag.html
git commit -m "fix: correction de l'affichage des tags vides suite à relecture"
git push
# La Pull Request sur GitHub se met à jour instantanément

6. Relire et merger une Pull Request

Si vous êtes le relecteur (ou que vous mergez votre propre PR après validation), voici le processus sur GitHub et en ligne de commande.

Relire sur GitHub

Dans l'onglet Files changed de la PR, vous pouvez commenter ligne par ligne. Trois options de review :

Approve
Le code est correct, la PR peut être mergée. Donne le feu vert.
Comment
Pose des questions ou laisse des suggestions sans bloquer le merge.
Request changes
Demande des modifications obligatoires avant d'autoriser le merge. Bloque la PR.

Choisir la stratégie de merge

GitHub propose trois boutons au moment de merger. Le choix impacte l'historique Git :

Merge commit
Crée un commit de fusion Merge pull request #X. Conserve l'intégralité des commits de la branche. Historique fidèle mais parfois verbeux.
Squash and merge
Regroupe TOUS les commits de la branche en un seul. Historique de main très propre. Idéal pour les petites features.
Rebase and merge
Rejoue les commits de la branche directement sur main. Historique linéaire, sans commit de merge. Avancé — peut réécrire les SHA.
💡 Pour un blog Django solo ou en petite équipe, Squash and merge est souvent le meilleur choix : un commit par fonctionnalité dans main, historique lisible, et vous pouvez committer autant de fois que vous voulez dans votre branche sans polluer l'historique principal.

Nettoyer après le merge

# Après avoir mergé la PR sur GitHub, mettre à jour main localement
git switch main
git pull

# Supprimer la branche locale (elle n'est plus utile)
git branch -d feat/tags

# Supprimer la branche distante sur GitHub (si pas fait automatiquement)
git push origin --delete feat/tags

# Voir les branches distantes supprimées qui traînent encore en local
git remote prune origin

7. Comprendre et résoudre les conflits de fusion

Un conflit survient quand Git ne peut pas fusionner automatiquement deux branches parce que les deux ont modifié le même endroit du même fichier de manière différente. Ce n'est pas une erreur — c'est Git qui vous dit : "j'ai deux versions, dis-moi laquelle garder."

Reproduire un conflit — scénario concret

Imaginons que vous et un collègue avez tous les deux modifié la fonction liste_articles dans blog/views.py sur des branches différentes :

feat/pagination — votre version
feat/filtres — version collègue
def liste_articles(request):
articles = Article.publies.all()
paginator = Paginator(articles, 10)
page = request.GET.get('page')
articles = paginator.get_page(page)
return render(request,
'blog/liste.html',
{'articles': articles})
def liste_articles(request):
tag = request.GET.get('tag')
articles = Article.publies.all()
if tag:
articles = articles.filter(
tags__slug=tag)
return render(request,
'blog/liste.html',
{'articles': articles})

Quand Git tente de fusionner ces deux branches dans main, il génère des marqueurs de conflit dans le fichier :

<<<<<<< HEAD (votre version)
def liste_articles(request):
    articles = Article.publies.all()
    paginator = Paginator(articles, 10)
    page = request.GET.get('page')
    articles = paginator.get_page(page)
    return render(request, 'blog/liste.html', {'articles': articles})
======= (séparateur)
def liste_articles(request):
    tag = request.GET.get('tag')
    articles = Article.publies.all()
    if tag:
        articles = articles.filter(tags__slug=tag)
    return render(request, 'blog/liste.html', {'articles': articles})
>>>>>>> feat/filtres (leur version)

Décoder les marqueurs de conflit

<<<<<<< HEAD
Début du bloc en conflit. Ce qui suit jusqu'au séparateur est votre version (branche courante).
=======
Séparateur entre les deux versions en conflit.
>>>>>>> feat/filtres
Fin du bloc en conflit. Ce qui précède est leur version (branche entrante).

Résoudre le conflit manuellement

La résolution consiste à supprimer les marqueurs et à écrire la version finale que vous souhaitez conserver. Ici, la bonne solution est de combiner les deux fonctionnalités — pagination ET filtrage par tag :

# blog/views.py — version résolue, combinant les deux apports
from django.core.paginator import Paginator

def liste_articles(request):
    tag = request.GET.get('tag')
    articles = Article.publies.all()

    # Filtre par tag (apport de feat/filtres)
    if tag:
        articles = articles.filter(tags__slug=tag)

    # Pagination (apport de feat/pagination)
    paginator = Paginator(articles, 10)
    page = request.GET.get('page')
    articles = paginator.get_page(page)

    return render(request, 'blog/liste.html', {
        'articles': articles,
        'tag_actif': tag,
    })

Finaliser la résolution

# 1. Vérifier qu'il ne reste plus de marqueurs de conflit
git diff

# 2. Ajouter le fichier résolu au staging
git add blog/views.py

# 3. Vérifier l'état — tous les conflits doivent être résolus
git status

# 4. Terminer le merge avec un commit
git commit -m "merge: résolution du conflit pagination + filtres dans liste_articles"

# 5. Pousser
git push
Ne faites jamais git commit -a après une résolution de conflits sans avoir relu le fichier. La commande -a stage automatiquement tous les fichiers modifiés — vous pourriez committer un fichier qui contient encore des marqueurs <<<<<<<, ce qui plantera votre application Django au démarrage.

Utiliser un outil visuel pour résoudre les conflits

Pour les conflits complexes (plusieurs zones dans un fichier, ou plusieurs fichiers), un outil graphique est très utile. VS Code gère nativement les conflits avec des boutons "Accept Current", "Accept Incoming", "Accept Both" :

# Ouvrir VS Code sur les fichiers en conflit
code .

# Ou configurer un outil de merge dédié (ex. vimdiff)
git config --global merge.tool vimdiff
git mergetool

Annuler un merge qui tourne mal

# Annuler un merge en cours (avant de committer)
git merge --abort

# Annuler un merge déjà committé (crée un commit d'annulation)
git revert -m 1 HEAD

8. Stratégies avancées : rebase, squash et stash

git rebase — réécrire l'historique pour le rendre linéaire

Le rebase déplace vos commits comme s'ils avaient été créés à partir de la dernière version de main. Le résultat est un historique linéaire et propre, sans commits de merge parasites.

# Depuis votre branche feat/auth, intégrer les nouveaux commits de main
git switch feat/auth
git rebase main

# En cas de conflit pendant le rebase :
# 1. Résoudre le conflit dans le fichier
# 2. git add fichier-resolu.py
# 3. git rebase --continue
# Pour annuler le rebase :
# git rebase --abort
⚠ Ne faites jamais git rebase sur une branche que d'autres personnes ont déjà clonée ou sur laquelle elles travaillent. Le rebase réécrit les SHA des commits — vous créeriez des divergences d'historique pour vos collègues. Réservez le rebase aux branches locales ou aux branches sur lesquelles vous êtes le seul à travailler.

git stash — mettre des modifications de côté

Vous êtes en plein milieu d'une modification et on vous demande de corriger un bug urgent sur main. git stash met vos modifications de côté comme dans une pile, vous laissant repartir d'un working directory propre :

# Mettre les modifications en cours de côté
git stash push -m "WIP: ajout pagination article"

# Vérifier la pile de stashs
git stash list
# stash@{0}: On feat/tags: WIP: ajout pagination article

# Basculer sur main, corriger, committer, revenir
git switch main
git switch -c fix/bug-urgent
# ... correction ...
git commit -m "fix: correction affichage date article"
git switch main
git merge fix/bug-urgent
git push

# Revenir sur feat/tags et récupérer le stash
git switch feat/tags
git stash pop     # applique le dernier stash et le supprime de la pile

git commit --amend — corriger le dernier commit

# Vous venez de committer mais vous avez oublié un fichier
git add blog/templates/blog/detail.html
git commit --amend --no-edit   # ajoute le fichier au dernier commit sans changer le message

# Corriger seulement le message du dernier commit
git commit --amend -m "feat: vue détail article avec meta SEO"
⚠ N'amendez jamais un commit déjà poussé sur GitHub. Cela réécrira l'historique et forcera un git push --force qui peut écraser le travail d'autres.

9. Cheatsheet — toutes les commandes de branches

CommandeDescription
git branch
Liste les branches locales (* = branche courante)
git branch -a
Liste toutes les branches (locales + distantes)
git branch -v
Liste les branches avec le dernier commit
git switch -c <nom>
Crée une branche et bascule dessus
git switch <nom>
Bascule sur une branche existante
git branch -d <nom>
Supprime une branche locale (seulement si mergée)
git branch -D <nom>
Force la suppression d'une branche non mergée
git push -u origin <nom>
Pousse une branche et crée le tracking distant
git push origin --delete <nom>
Supprime une branche sur le dépôt distant
git merge <branche>
Fusionne une branche dans la branche courante
git merge --abort
Annule un merge en cours avec conflits non résolus
git rebase main
Rebase la branche courante sur main
git rebase --continue
Continue le rebase après résolution d'un conflit
git rebase --abort
Annule un rebase en cours
git stash push -m "msg"
Met les modifications en cours de côté avec un label
git stash list
Liste tous les stashs enregistrés
git stash pop
Réapplique le dernier stash et le supprime de la pile
git stash drop stash@{0}
Supprime un stash précis sans l'appliquer
git log --oneline --graph --all
Visualise l'historique de toutes les branches en ASCII
git diff main...feat/auth
Voir toutes les modifications de feat/auth par rapport à main
git commit --amend --no-edit
Ajoute des fichiers au dernier commit sans changer le message
git remote prune origin
Nettoie les branches distantes supprimées dans les références locales

Conclusion : les branches, le vrai superpouvoir de Git

Vous savez maintenant créer des branches pour chaque fonctionnalité, travailler en parallèle sans vous bloquer mutuellement, soumettre votre travail via des Pull Requests documentées, et résoudre les conflits de fusion avec méthode. Ces compétences transforment radicalement la façon de travailler — seul comme en équipe.

La prochaine étape logique de cette série est le déploiement en production : configurer PostgreSQL à la place de SQLite, installer Gunicorn comme serveur WSGI, et servir votre blog Django avec Nginx sur un serveur Linux. Ce sera l'objet du prochain article.

Mots-clés : git branch django, git feature branch workflow, pull request github tutoriel, résoudre conflit git, git merge vs rebase, git stash utilisation, git switch, merge conflict markers, squash and merge github, git log graph