<!--
  canonical-url: https://www.avonture.be/fr/blog/git-branches-gst
  published:     2026-04-06
  This is a static plain-Markdown mirror generated at build time.
  Visit the canonical URL above for the fully rendered page, with images and interactive components.
-->

# Afficher les 3 dernières branches modifiées quand vous entrez dans un repo git

> Découvrez comment configurer ZSH pour afficher automatiquement vos branches Git les plus récemment modifiées dès que vous entrez dans un repository ou que vous vérifiez son statut.

---
![Afficher les 3 dernières branches modifiées quand vous entrez dans un repo git](/img/v2/git_branches_status.webp)

**TL;DR**Revenir sur un ancien projet, c'est souvent travailler par accident sur la mauvaise branch Git et provoquer des conflits de merge. Pour éviter ça, vous pouvez ajouter un script maison à votre configuration Zsh qui affiche automatiquement les trois branches locales modifiées le plus récemment. Cette fonction s'accroche aux changements de répertoire et à la commande git status pour vous rappeler instantanément votre contexte de travail précédent.

Zut, c'est encore arrivé : je travaillais sur un gros projet (composé de plusieurs repositories), j'ai poussé mon travail en production (sur la branch `main`), puis j'ai commencé un refactoring dans une branch appelée `wip`. Ensuite... je suis passé à d'autres projets, comme toujours.

Des mois plus tard, en revenant sur le projet, j'ai passé quelques jours à faire des améliorations sur la branch `main`. Et puis — zut ! Vous l'avez devinée — la dernière branch active n'était pas `main`, mais `wip`. Aaaargh... J'avais donc maintenant deux branches divergentes, `main` et `wip`, et des conflits de merge à gérer. Tellement frustrant, surtout que j'étais le seul développeur sur le projet.

Comment éviter ce genre de situation ?

*Deux autres approches du même problème : trier les branches par date de manière globale avec [Git - Some tips for your .gitconfig file](/blog/git-config), et [git worktree](/blog/git-worktree), qui permet aux deux branches d'exister en même temps dans des dossiers séparés, ce qui rend la confusion impossible.*

## Un exemple concret {#a-real-world-example}

Mon disque est rempli de projets et d'une énorme liste de repositories. Une fois le bloc ci-dessous en place (on y vient), dès que je fais un `cd` dans l'un d'eux, j'obtiens automatiquement les trois dernières branches. Aucun effort supplémentaire :

```bash
❯ cd project/subproject

=== Recent Local Branches ===
  feature_logging - 1 month ago
* main - 3 months ago
  wip - 2 months ago
```

*(`*` indique la branch active)*

Je réalise immédiatement *« Ah oui, je travaillais sur `wip` »*, et je peux basculer sur la bonne branch. La même information apparaît avec `gst`, l'alias que j'utilise pour `git status`.

## Le voir en action avec Docker {#seeing-it-in-action-with-docker}

Aucune installation nécessaire pour `git` lui-même — ce qui vaut la peine d'être essayé sans risque, c'est le hook. Le container jetable ci-dessous contient déjà le piège exact de la « mauvaise branch » décrit en intro : `main` est checked out (la plus ancienne des trois), tandis que `wip` et `feature_logging` ont tous deux été touchés plus récemment.

> **Pourquoi Docker d'abord ?:**
>
> Le Dockerfile ajoute le bloc exact ci-dessous à `~/.zshrc` et construit un repo avec trois branches commitées à des dates différentes. Rien n'est touché sur votre propre machine ni dans vos repos.

**Dockerfile**

```docker
FROM ubuntu:24.04

ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && \
    apt-get install -y --no-install-recommends \
        zsh \
        git && \
    rm -rf /var/lib/apt/lists/*

# ~/.zshrc — the exact block from this article, appended verbatim
RUN printf '%s\n' \
    '# !!! PUT THIS NEW BLOCK AT THE END OF YOUR ~/.zshrc FILE' \
    '# !!! DON'\''T REPLACE ALL THE FILE, JUST APPEND' \
    '' \
    '# Core function to display the 3 most recently modified local branches' \
    '_display_recent_git_context() {' \
    '    # Fallback immediately if the current directory is not a git repository' \
    '    if ! command git rev-parse --is-inside-work-tree >/dev/null 2>&1; then' \
    '        return 0' \
    '    fi' \
    '' \
    '    # Fallback if the repository is entirely empty (no commits yet)' \
    '    if ! command git rev-parse HEAD >/dev/null 2>&1; then' \
    '        return 0' \
    '    fi' \
    '' \
    '    local current_branch' \
    '    current_branch=$(command git rev-parse --abbrev-ref HEAD 2>/dev/null)' \
    '' \
    '    echo $'\''\e[1;36m=== Recent Local Branches ===\e[0m'\''' \
    '' \
    '    # Extract and format the 3 most recently modified local branches' \
    '    command git for-each-ref --sort=-committerdate refs/heads/ \' \
    '        --format="%(if:equals=${current_branch})%(refname:short)%(then)%(color:green)* %(refname:short)%(color:reset)%(else)  %(color:yellow)%(refname:short)%(color:reset)%(end) - %(color:blue)%(committerdate:relative)%(color:reset)" \' \
    '        | head -n 3' \
    '' \
    '    echo ""' \
    '}' \
    '' \
    '# Smart wrapper for git status' \
    'unalias gst 2>/dev/null' \
    'gst() {' \
    '    _display_recent_git_context' \
    '    command git status "$@"' \
    '    return $?' \
    '}' \
    '' \
    '# Triggered automatically by Zsh on directory change' \
    'chpwd() {' \
    '    _display_recent_git_context' \
    '}' \
    >> /root/.zshrc

# Build a repo mirroring the article's own example: `main` is checked out
# (the oldest of the three, ~3 months old) while `wip` and `feature_logging`
# were both touched more recently — the exact "wrong branch" trap.
RUN git config --global user.email "demo@example.com" && \
    git config --global user.name "Demo" && \
    mkdir -p /root/projects/my-blog && \
    cd /root/projects/my-blog && \
    git init -q -b main && \
    export GIT_AUTHOR_DATE="$(date -d '-100 days' '+%Y-%m-%d 12:00:00')" && \
    export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" && \
    echo "# Demo repo" > README.md && \
    git add README.md && \
    git commit -q -m "Initial commit" && \
    git checkout -q -b wip && \
    export GIT_AUTHOR_DATE="$(date -d '-70 days' '+%Y-%m-%d 12:00:00')" && \
    export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" && \
    echo "wip notes" > NOTES.md && \
    git add NOTES.md && \
    git commit -q -m "wip: refactor attempt" && \
    git checkout -q main && \
    git checkout -q -b feature_logging && \
    export GIT_AUTHOR_DATE="$(date -d '-45 days' '+%Y-%m-%d 12:00:00')" && \
    export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE" && \
    echo "console.log(1)" > logging.js && \
    git add logging.js && \
    git commit -q -m "feat: add logging" && \
    git checkout -q main

WORKDIR /root

# Signal clearly that this shell runs inside a container
RUN echo "PROMPT='%B%F{yellow}🐳 %F{cyan}%n%f%b %~ %# '" >> /root/.zshrc

CMD ["/bin/zsh", "-i", "-l"]
```

Buildez-le et lancez-le :

```bash
$ docker build -t gst-demo .
[+] Building 18.6s (5/5) FINISHED
✔ exporting to image
$ docker run --rm -it gst-demo
🐳 root ~ # cd projects/my-blog
```

`chpwd` se déclenche à l'instant où vous faites `cd` — aucune commande supplémentaire à taper :

```bash
=== Recent Local Branches ===
  feature_logging - 6 weeks ago
  wip - 2 months ago
* main - 3 months ago
```

`main` (checked out, marquée `*`) est la *plus ancienne* des trois — exactement le piège de l'intro. Lancez ensuite `gst` : la même bannière apparaît, immédiatement suivie du vrai `git status`.

## Installation {#installation}

Comme je passe l'essentiel de mon temps dans le terminal, j'ai besoin d'une notification dès que j'entre dans le dossier d'un projet ou que je lance une commande `git status`.

Éditez simplement votre fichier `~/.zshrc` et ajoutez ce bloc à la fin (ou, mieux, mettez-le dans son propre fichier comme expliqué dans [Beyond the Monolith - Organizing Your ZSH Workflow Like a Pro](/blog/modular-zsh-workflow)) :

**\~/.zshrc**

```bash
# !!! PUT THIS NEW BLOCK AT THE END OF YOUR ~/.zshrc FILE
# !!! DON'T REPLACE ALL THE FILE, JUST APPEND

# Core function to display the 3 most recently modified local branches
_display_recent_git_context() {
    # Fallback immediately if the current directory is not a git repository
    if ! command git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
        return 0
    fi

    # Fallback if the repository is entirely empty (no commits yet)
    if ! command git rev-parse HEAD >/dev/null 2>&1; then
        return 0
    fi

    local current_branch
    current_branch=$(command git rev-parse --abbrev-ref HEAD 2>/dev/null)

    echo $'\e[1;36m=== Recent Local Branches ===\e[0m'

    # Extract and format the 3 most recently modified local branches
    command git for-each-ref --sort=-committerdate refs/heads/ \
        --format="%(if:equals=${current_branch})%(refname:short)%(then)%(color:green)* %(refname:short)%(color:reset)%(else)  %(color:yellow)%(refname:short)%(color:reset)%(end) - %(color:blue)%(committerdate:relative)%(color:reset)" \
        | head -n 3

    echo ""
}

# Smart wrapper for git status
unalias gst 2>/dev/null
gst() {
    _display_recent_git_context
    command git status "$@"
    return $?
}

# Triggered automatically by Zsh on directory change
chpwd() {
    _display_recent_git_context
}
```

## Comment ça fonctionne {#how-it-works}

La fonction `_display_recent_git_context` lance `git rev-parse --is-inside-work-tree` pour vérifier que vous êtes bien dans un repository Git. Si ce n'est pas le cas, elle s'arrête silencieusement. Si vous êtes dans un repo, elle lance `git rev-parse HEAD` pour vérifier si le repository est complètement vide. Si c'est le cas, il n'y a rien à faire, donc elle s'arrête.

Si le repository n'est pas vide, la fonction récupère le nom de la branch active, puis lance `git for-each-ref` pour parcourir vos branches **locales**, en les triant par date de leur dernier commit en ordre décroissant (`--sort=-committerdate`).

Le script définit également (ou écrase) un alias ZSH `gst`. La nouvelle commande `gst` appelle notre fonction maison avant d'exécuter la commande native `git status`.

Enfin, puisqu'on utilise ZSH, on peut tirer parti d'un hook standard appelé `chpwd` (qui se déclenche lors du *changement du répertoire de travail courant*). Ce hook est appelé automatiquement par des commandes système comme `cd` ou `pushd`.

## Branches locales uniquement {#local-branches-only}

Il est important de noter que ce script interroge exclusivement votre repository Git local. Il évite délibérément d'exécuter une commande `git fetch` pour vérifier les mises à jour sur le serveur distant.

Déclencher une requête réseau à chaque changement de répertoire introduirait une latence inacceptable et ralentirait fortement votre workflow dans le terminal. Comme il lit strictement vos références Git locales — ce qui est différent de la zone de staging Git qui suit vos modifications de fichiers non commitées — le script s'exécute instantanément.

Si un collègue pousse une nouvelle branch sur le repository distant, elle n'apparaîtra pas automatiquement dans votre liste de branches récentes. Vous gardez le contrôle total des opérations réseau ; dès que vous voulez mettre à jour votre repository local avec l'activité distante la plus récente, vous lancez simplement `git fetch` manuellement.

## Conclusion {#conclusion}

Maintenant, chaque fois que je lance un `cd` et que j'entre dans un projet Git, je n'ai aucune commande supplémentaire à taper — j'obtiens immédiatement l'information dont j'ai besoin : le nom des branches actives les plus récentes.

Ce snippet est assez petit pour vivre directement dans `~/.zshrc`, mais si vous accumulez plusieurs hooks et fonctions comme celui-ci, voyez [Beyond the Monolith - Organizing Your ZSH Workflow Like a Pro](/blog/modular-zsh-workflow) pour une façon plus propre de les organiser.
